暑假test总结1

抢匪的财宝

 (treasure.pas/c/cpp)

【问题描述】

古威市长和韩丁纳市长都争着要找到抢匪野蛮老危的地图宝藏,放在自己城市的展览馆里。汪汪队帮助古威市长抢先一步找到了藏宝藏的地方。

宝藏埋在洞穴中长为n, 宽为m的矩形地面下。汪汪队从洞穴入口A(图中左上角)进入,从洞穴出口B(图中右下角)离开,每步只能向下走或向右走,当走到[x,y]方格时,可取出[x,y]方格及与[x,y]相邻的上下左右四个方向方格中的全部财宝。每个方格中的数表示财宝数量,如下图所示:

 

汪汪队从图中A[1,1]走到B[5,5]格时:

通过的路径是[1,1]→[2,1]→[3,1]→[4,1]→[4,2]→[4,3]→[4,4]→[4,5]→[5,5]

最多能取到财宝的数量为:0+1+2+1+0+3+0+0+8+5+0+5+0+6+7+0+8+8+6=60

【输入文件】

输入文件treasure.in中有n+1行。

第1行包含2个用空格分开的正整数n、m,分别表示洞穴中埋藏财宝地面的长和宽;接下来的n行,每行m个用空格分隔的正整数,表示各个格子中的财宝数量。

【输出文件】

输出文件treasure.out中有1个正整数,是汪汪队最多能取到的财宝数量。

【输入输出样例】

treasure.in

treasure.out

5 5

0 1 2 0 5

2 1 3 0 0

0 3 6 8 6

0 0 5 5 7

8 0 0 0 8

60

 

【数据规模】

100%的数据:1≤n,m≤500。

分析

一看就知道是一道动归 

但是要取旁边的,怎么知道取过没有呢

然后就冥思苦想

看见动归题总想着,定义f[N][N],然后想转移方程,

然后发现怎么转都转移不过来

现在筋转过来了

增加状态呀

n=500,吝啬什么,要用什么加什么

其实增加一个从哪个方向转移过来就好了

f[i][j][0]=
max(f[i-1][j][0]+a[i+1][j]+a[i][j+1]+a[i][j-1],f[i-1][j][1]+a[i+1][j]+a[i][j+1]);
f[i][j][1]=
max(f[i][j-1][1]+a[i+1][j]+a[i][j+1]+a[i-1][j],f[i][j-1][0]+a[i+1][j]+a[i][j+1]);

两行的转移方程…………

聚会  

 给你 n 个点,任意两点之间有唯一路径可到达 
 每个点有一个点权 Ci 
 每条边有个边权 Wi 

问:找出一个点作为聚会点,使得所有其他点到该点代价总和最小,代
价=点权*路径。如:i 作为集合点,点 x 的代价为 Cx*Len(x,i) ,Len(x,i)
表示 x 的代价为 Cx*Len(x,i) ,Len(x,i)表示 x 到 i 的距离。 

【输入样例】 
51 1 0 0 21 3 12 3 23 4 34 5 3【输出样例】 
15 
  
【数据规模】 
30%数据:N<=30 
100%数据:N<=100000,0<=Ci,W<=1000

分析

看出来是一棵树,打死没想到树形dp(换根)

然后写了个暴力

其实很简单,暴力就是枚举每个集合点,然后dfs

但很多都是重复的

我们换一个根,子树很多东西可以直接用

void Dfs(LL cur,LL fa)
{
	for(LL i=first[cur];i;i=next[i]){
		LL t=to[i];
		if(t==fa) continue;
		f[t]=f[cur]+(sum-num[t])*w[i]-num[t]*w[i];
		ans=min(ans,f[t]);
		Dfs(t,cur);
	}
}

容斥原理真是个好东西

发现树形dp好多都有一个总子树和

减一下,就是剩下的和。

一定要学会这种思想,实现很容易

数字游戏

alice 和 bob 又在玩一个游戏。他们从一个数字 X0>=3,开始,期望到很大的数字。游戏是这样的:

alice 先走,然后轮流。在第 i 个回合中,轮到的玩家找一个小于当前数字的素数, 然后选择大于当前数字且是找的素数的倍数。即选择的素数 P<Xi-1,Xi>=Xi-1,Xi 是 P 的倍数,注意如果 P 是 Xi-1 的约数,那么数字不会变。

L 知道了他们两轮后的状态,现在给你一个 X2 表示两轮后选择的数字,请你确定最小的起始数字 X0。特别提醒,玩家不一定每一步选择是最聪明的,你应该考虑所有可能的情况。

【输入】

输入一个整数 X2,保证 X2 是合数

【输出】

输出一个整数 X0

【输入样例 1】14

【输出样例 1】6

【样例解释】

X0=6,

第一轮:alice 选择素数 5,并决定这轮数字X1=10

第一轮:BOB 选择素数 7,并决定这轮数字 X2=14

分析

推一下,我们发现,需要求每个数的最大z质约数

void pre(int n){
	for(int i=2;i<=n;i++){
		if(isp[i]==0){
			prim[++cnt]=i;//质数 
			p[i]=i;	//最大质因子
		}
		for(int j=1;j<=cnt&&prim[j]*i<=n;j++){//筛 i*比i小的质数 ->i是最大因子
			isp[prim[j] * i]=1;
			p[prim[j] * i]=p[i];//最大质因子,每个数是由最大因子筛选出 
			/*一个数的最大质因子是它的最大因子的最大质因子
			这个break保证了合数只被最小质约数访问到。 
                        比如40=2*20=4*10=5*8,只有i=20时,才会在prim[j]=2的时候被访问到。 
                        当i=10时,在prim[j]=2时就已经被break了;同样的,i=8时,在prim[j]也已经break
			*/ 
			if(i%prim[j]==0)break;
		}
	}
}

线性筛一定要会,处理质数的利器。

序列切割

给定一个长度为 n 的序列 ai,和一个数字 c。你需要将这个序列切成若干段,对于每一个长度为 k 的数字段,这段中最小的 k/C 个数字(向下取整)都会自动删除,问如何切割使得最后剩下的数字和最小,最小是多少?

如序列[3,1,6,5,2]当 C=2,这是 3+6+5=14

【输入】

第一行是 2 个整数 N,C

第二行是 N 个整数 Ai

【输出】

一个整数,最小值

【样例输入】12 10

1 1 10 10 10 10 10 10 9 10 10 10

【样例输出】92

【样例解释】

其中一个最佳分区分别是[1,1],[10,10,10,10,10,10,10,10,10,10],其值分别为 2 和 90。

100%数据 n,c<=100000,1<=ai<=10^9

分析

dp

方程基本推对了,但考虑复杂了

标程是st+dp,很短

#include<bits/stdc++.h>
#define N 100005
#define LL long long
using namespace std;
LL n,c,num,a[N],st[N][30],f[N],Log[N];
LL read(){
	LL cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){ch=getchar();if(ch=='-') f=-1;}
	while(isdigit(ch)) cnt=cnt*10+(ch-'0'),ch=getchar();
	return f*cnt;
}
LL rmq(LL l,LL r){
   LL x=Log[r-l+1];
   return min(st[l][x],st[r-(1<<x)+1][x]);
}
int main()
{
	n=read(),c=read();
	for(LL i=1;i<=n;i++){
	  a[i]=read(),num+=a[i],st[i][0]=a[i];
	  if(i==1) continue;
	  Log[i]=Log[i/2]+1;
    }
    if(n<c){cout<<num;return 0;}
    for(LL j=1;(1<<j)<=n;j++)
      for(LL i=1;i<=n;i++)
      	st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    for(LL i=c;i<=n;i++)
      f[i]=max(f[i-1],f[i-c]+rmq(i-c+1,i));
    cout<<num-f[n];
    return 0;
}

现在有个毛病,遇到dp就紧张,总觉得转移方程是什么高大尚的东西

其实可以这么想

假设我要求一个东西,之前所有东西都有,我想要什么要什么

比如这道,结合样例

f[12]要么更f[11]一样,要么自己再加一段,即f[7]+min(a[i])(8<=i<=12) 

而且f[i]一定是i的最优解,不要想多

多练练手啊

这篇博客写得真tm好

多米诺骨牌

有 n 个多米诺骨牌,从左到右排列,每一个骨牌都有一个高度 Li,向右推倒,它会直接向右倒下,如下图,倒下后该骨牌的顶端落在 Xi+Li  的位置,(Xi  是它位于的坐标,即倒下时该骨牌不会发生移动)

在倒下过程中,骨牌会碰到其他骨牌,碰到的骨牌会向右倒,如下图,最左边的骨牌倒下会碰倒 A,B,C,A,B,C 会倒下,但是不会直接碰到 D,但是 D 会因为 C 的倒下而碰倒。

在给你 N 个骨牌的坐标 Xi,和每个骨牌的高度 Li。则一个骨牌能碰倒另一个骨牌当切仅当 xi+li≥xj。同时有 Q 个询问 [L,R],问向右推到第 L 个骨牌,最少需要多少代价让 R 倒下。你可以临时增加某个骨牌的高度,增加 1 个高度的代价是 1.

6

1 5

3 3

4 4

9 2

10 1

12 1

4

1 2

2 4

2 5

2 6

输出样例

0

1

1

2

20%数据:N,Q<=1000,Xi<=10000

40%数据:N,Q<=10000,Xi<=100000

100%数据:2<=N<=100000,1<=Q<=2000000,Xi<=10^9

分析

栈+并查集

特别巧妙

将询问的左端点存在vector

然后倒着处理

能合并就合并

顺便处理前缀和

O1询问

#include<bits/stdc++.h>
/*
这题等于是求一段区间内有多少长度没被覆盖

把多米诺骨牌看成区间,按照倒序处理每个区间,看成是每次这个区间与后面的一些区间并成连通块,
处理x为当前区间的查询,这需要知道y属于哪个连通块,用栈+并查集维护,然后再维护一个未覆盖长度的后缀和,
就可以O1来回答询问,并成连通块的时候顺便更新这个后缀和
*/
using namespace std;
#define ll long long
#define N 200010
#define in read()
int  read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
struct Node{
	int l,r;
}a[N],b[N];
vector<int>g[N];
int n,m;
stack<int> S;
int fa[N],l[N],r[N];
ll sum[N],ans[N];


int find(int x){
	if(x!=fa[x])fa[x]=find(fa[x]);return fa[x];
}
inline void solve(int x,int id){
    int tmp=b[id].r;
    ans[id]=sum[x]-sum[find(tmp)];
}
int main(){
	n=in;
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=n;i++){
		a[i].l=in;
		a[i].r=a[i].l+in; 
		//i骨牌的区间 
	}
	m=in;
	for(int i=1;i<=m;i++){
		b[i].l=in;b[i].r=in;
		g[b[i].l].push_back(i);
	}
	for(int i=n;i>=1;i--){
		l[i]=a[i].l;r[i]=a[i].r;
		while(!S.empty() && l[S.top()] <=r[i]){
			r[i]=max(r[i],r[S.top()]);
            fa[find(S.top())]=i;
            S.pop();
		}//与栈顶合并一个联通块 
		if(!S.empty()) sum[i]=sum[S.top()] + l[S.top()]-r[i];//如果栈有元素,说明后面有联通快,则统计后缀和 
		else sum[i]=0;//后缀和记录是当前点到最后的不联通数量 
		S.push(i);
		int len= g[i].size();
		for(int j=0;j<len;j++)//统计当前点做为左端点的询问答案 
            solve(i,g[i][j]);	
	}
	 for(int i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/81347052