Noip2017普及组

T4 跳房子 二分答案 单调队列

跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
  跳房子的游戏规则如下:
  在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内有一个数字( 整数),表示到达这个格子能得到的分数。玩家第一次从起点开始向右跳, 跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
  现在小 R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 d。小 R 希望改进他的机器人,如果他花 g 个金币改进他的机器人,那么他的机器人灵活性就能增加 g, 但是需要注意的是,每次弹跳的距离至少为 1。 具体而言, 当g < d时, 他的机器人每次可以选择向右弹跳的距离为 d-g, d-g+1,d-g+2, …, d+g-2, d+g-1, d+g; 否则( 当g ≥ d时),他的机器人每次可以选择向右弹跳的距离为 1, 2, 3, …, d+g-2, d+g-1, d+g。
  现在小 R 希望获得至少 k 分,请问他至少要花多少金币来改造他的机器人。

我们定义 dp [ i ] 表示跳到 i 时,总数字之和的最大值。那么转移方程就比较容易了,dp [ i ] = max { dp [ j ] } + s [ i ](x [ i ] - r ≤ x [ j ] ≤ x [ i ] - l),这样做 O ( n ^ 2 ) 应该至少能拿到 50 分。下面,我来讲讲如何优化,可以过 500000 。P.S. x 应该是增加了代码的复杂度吧……

(请自行脑补一下 )当 i 不断递增向后走时,前面一定跟着一个区间,表示满足 x [ i ] - r ≤ x [ j ] ≤ x [ i ] - l 的 j。每次 i 加一后,会有头上部分 j 退出区间,也会有尾部部分 j 加入区间。此时,如果重新遍历区间求最大值,显然有些浪费了。我们维护好一个单调队列,包含区间内的部分 j,使得队头(左)的 dp 值较大,队尾(右)的 dp 值较小,每次取队头的为最大值。

如果存在一个 j,其后面有比自己 dp 值更大的 j',其实 j 是无用的。为什么呢?j 显然会比 j' 更早退出区间,当 j 在区间时,j' 显然会比 j 更优些。因此,单调队列中会自动滤掉无用的 j,保持 j 单调的同时 dp [ j ] 单调。接下来需要考虑的问题,就是如何维护了。

当 i 加一时,把区间右端的 j,即一直递增的游标 L,加入到队尾中。不过,应先将比 dp [ j ] 更小的(无用的)队尾弹出。之后,把队头已不在区间范围内的 j 弹出,剩下的队头就是所求的最大值了,更新入 dp [ i ]!在存在 i 使得 dp [ i ] ≥ k 时,就说明当前的 g 是符合条件的。

这个单调队列可以手动维护,但我使用了 STL 中的 deque,比较方便。我们再考虑一下复杂度,由于每个 j 只会进队一次、出队一次,时间复杂度应该是 O ( n ) 的。再乘上二分带的 log,显然可以过。

#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<map>
const int maxn=500005;
using namespace std;
//int n,d,k;
//int len[maxn],value[maxn],dp[maxn],que[maxn*2];
//(因为单调)二分+动态规划+(用单调队列优化) 
//我觉得单调队列最难理解
//当每次i加1后,会有上面部分j退出区间,也会有尾部的j加入区间,此时如果重新遍历区间求最大值就会浪费---所以维护一个单调队列
//包含区间内的部分j,使得对头的dp最大,队尾的dp最小,每次取队头的位最大值
//单调队列会自动过滤无用的j,保持dp[j]单调 
/*
int maxcost(int a){
	memset(dp,-127,sizeof(dp));
	int head=1,tail=0;
	int mi=max(1,d-a);
	int mx=d+a;
	int rt=0,fir=-1;
	que[++tail]=0,dp[0]=0;
	for(int i=1;i<=n;i++){
		if(len[i]<mi) continue;
		if(len[i]>=mi&&fir==-1) fir=i;
		if(len[i]-len[i-1]>mx) break; //过不去 
		while(len[i]-len[fir]>=mi&&fir<i){
			while(head<=tail&&dp[fir]>dp[que[tail]]) tail--;  //把无用的队尾丢掉 
			if(dp[fir]!=-0x7f7f7f7f) que[++tail]=fir;  //把对头的弄到队尾来? 
			fir++;
		}
		while(head<=tail&&len[que[head]]+mx<len[i]) head++;  //把无用的队头丢掉 
		if(head>tail) dp[i]=-0x7f7f7f7f;
		else dp[i]=dp[que[head]]+value[i];
		if(dp[i]>rt) rt=dp[i];
	}
	return rt;
}
*/
//上面是单调队列的写法,还可以用deque双端队列
//下面直接用动态规划也可以

long long f[maxn],a[maxn][2],n,d,k,lpos,rpos;
bool check(int g){
	lpos=d-g;rpos=d+g;
	if(lpos<=0) lpos=1;
	memset(f,-127,sizeof(f));//先把dp数组初始化为一个很小的值
	f[0]=0 ; //起点
	for(int i=1;i<=n;i++){
		for(int j=i-1;j>=0;j--){ //用前面的去优化后面的 
			if(a[i][0]-a[j][0]<lpos) continue; //太靠左
			if(a[i][0]-a[j][0]>rpos) break; //太靠右
			f[i]=max(f[j]+a[i][1],f[i]);
			if(f[i]>=k) return 1; 
		}
	} 
	return 0;
}

int main(){
	cin>>n>>d>>k;
	/*
	for(int i=1;i<=n;i++){
		cin>>len[i]>>value[i];
	}
	int left=0,right=len[n];
	while(left!=right){
		int mid=(left+right)>>1;
		if(maxcost(mid)>=k) right=mid;
		else left=mid+1;
	}
	if(maxcost(left)<k) cout<<"-1"<<endl;
	else cout<<left<<endl; 
	*/
	for(int i=1;i<=n;i++){
		cin>>a[i][0]>>a[i][1];
	}
	int ans=-1,l=0,r=20001,m=(l+r)/2;
	while(l<=r){
		if(check(m)){
			ans=m;
			r=m-1;
		}
		else l=m+1;
		m=(l+r)/2;
	}
	cout<<ans<<endl;
return 0;
}

  T3 

棋盘

 这道题是用DFS写的,因为要记录上一个格子的情况(有没有变色)所以参数需要三个,而且需要回溯,从而改变那个可不可以的参数

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
int mapp[110][110];
int cost[110][110]; //这个存放的是到这里的最小花费 
int m,n,x,y,sig;
int tot=0;
int dis[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
void  dfs(int x,int y,int flag){ //flag是能不能走的标志
	if(x==m&&y==m) return;
	for(int i=0;i<4;i++){
		int xx=x+dis[i][0],yy=y+dis[i][1];
		if(xx>=1&&xx<=m&&yy>=1&&yy<=m){
			int ans=-1; //表示花费
			if(mapp[xx][yy]==mapp[x][y]&&mapp[xx][yy]!=0) ans=0;
			else{
				if(mapp[xx][yy]!=0) ans=1; //如果颜色不同
				if(!mapp[xx][yy]&&flag)  ans=2;  //如果没有颜色,而且可以变色 
			}
			if(ans!=-1&&(cost[x][y]+ans<cost[xx][yy]||!cost[xx][yy])){
				cost[xx][yy]=cost[x][y]+ans; //如果现在到那里去的代价小于他本来的代价,或者还没有去过
				if(ans==2){
					mapp[xx][yy]=mapp[x][y];
					dfs(xx,yy,0);
					mapp[xx][yy]=0; //要回溯 
				} 
				else dfs(xx,yy,1);
			}
		}
	}

}
int main(){
	cin>>m>>n;
	memset(mapp,0,sizeof(mapp));
	for(int i=0;i<n;i++){
		cin>>x>>y>>sig;
		mapp[x][y]=sig+1;
	}
	cost[1][1]=1;
	dfs(1,1,1);
	cout<<cost[m][m]-1<<endl;
	return 0;
}
	/*
	int flag=0,flag1=0;
	if(x==m&&y==m) return tot;
	for(int i=0;i<4;i++){
		int xx=x+dis[i][0],yy=y+dis[i][1];
		if(xx>=1&&xx<=m&&yy>=1&&yy<=m&&mapp[xx][yy]==tag){
			cout<<xx<<" "<<yy<<" "<<tag<<" "<<tot<<endl;
			flag=1;
			dfs(xx,yy,tag,tot);
			
		}
	}
	if(flag==0){
		for(int i=0;i<4;i++){
		int xx=x+dis[i][0],yy=y+dis[i][1];
		if(tag==1) tag=0;
		else tag=1;
		if(xx>=1&&xx<=m&&yy>=1&&yy<=m&&mapp[xx][yy]==tag){
				cout<<xx<<" "<<yy<<" "<<tag<<" "<<tot<<endl;
				flag1=1;
			dfs(xx,yy,tag,tot+1);
		}
	} 
	if(flag1==0){
		for(int i=0;i<4;i++){
		int xx=x+dis[i][0],yy=y+dis[i][1];
		for(int j=0;j<4;j++){
			int xxx=xx+dis[j][0];int yyy=yy+dis[j][1];
			if(xxx>=1&&xxx<=m&&yyy>=1&&yyy<=m&&mapp[xxx][yyy]!=-1) {
					cout<<xx<<" "<<yy<<" "<<tag<<" "<<tot<<endl;
				dfs(xx,yy,mapp[xxx][yyy],tot+2);
			} 
		} 
	}
	}
	}
	
	return -1;
	*/

  

原文地址:https://www.cnblogs.com/shirlybaby/p/12241077.html