飞扬的小鸟

洛咕

题意:(Flappy) (Bird)是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。为了简化问题,我们对游戏规则进行了简化和改编:游戏界面是一个长为 (n),高为 (m) 的二维平面,其中有 (k) 个管道(忽略管道的宽度)。 小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。小鸟每个单位时间沿横坐标方向右移的距离为 (1),竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度 (X),每个单位时间可以点击多次,效果叠加;如果不点击屏幕,小鸟就会下降一定高度 (Y)。小鸟位于横坐标方向不同位置时,上升的高度 (X) 和下降的高度 (Y) 可能互不相同。小鸟高度等于 (0) 或者小鸟碰到管道时,游戏失败。小鸟高度为 (m) 时,无法再上升。现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙.(5 leq n leq 10000), (5 leq m leq 1000)(0 leq k < n), (0 < X < m), (0 < Y < m), (0 < P < n), (0 leq L < H leq m), (L + 1 < H).

分析:数据范围提示时间复杂度在(O(nm))左右??设(f[i][j])表示到达点((i,j))的最少点击屏幕数.则(f[i][j]=min(f[i][j-down[i-1]],f[i-1][j-k*up[i-1]]+k)),其中(down[i-1])表示在(i-1)处不点击屏幕下降的距离,(up[i-1])表示在(i-1)处点击屏幕上升的距离.这样的话时间复杂度是(O(nmk)),(k)大一点就会超时了.

考虑把(k)优化掉,发现(k)的意思其实就是连着跳(k)次,而我们跳第(k)次显然可以直接从跳第(k-1)(O(1))转移过来.因为我们从小到大枚举纵坐标(j),所以更新(f[i][j])(f[i][j-up[i-1]])已经更新完了,我们直接从那里转移过来即可.

然后还有一些细节问题放注释里面了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=10005;
const int M=1005;
int n,m,k,sum,ans=1e9,X[N],Y[N];
int up[N],down[N],bj[N],f[N][M];
inline bool check(int i){
	for(int j=1;j<=m;++j)
		if(f[i][j]!=1e9)return 1;
	return 0;
}
int main(){
	n=read();m=read();k=read();
	for(int i=0;i<n;++i)up[i]=read(),down[i]=read();
	for(int i=1;i<=k;++i){
		int pos=read();bj[pos]=1;X[pos]=read();Y[pos]=read();
	}
	for(int i=1;i<=n;++i)
    	for(int j=0;j<=m;++j)f[i][j]=1e9;
//i=0时任一位置都是0步,因为题目说了可以从最左边任意整数高度位置出发,其余初始化正无穷
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(j-up[i-1]>=1){
				f[i][j]=min(f[i][j],f[i-1][j-up[i-1]]+1);//跳一次
				f[i][j]=min(f[i][j],f[i][j-up[i-1]]+1);//连着跳
			}
		}
		for(int j=m-up[i-1];j<=m;++j){//飞到顶部的时候要单独考虑
			f[i][m]=min(f[i][m],f[i-1][j]+1);//跳一次
			f[i][m]=min(f[i][m],f[i][j]+1);//连着跳
		}
		for(int j=1;j+down[i-1]<=m;++j)//不跳,直接下降过来
			f[i][j]=min(f[i][j],f[i-1][j+down[i-1]]);
//不能把不跳的情况与跳的情况一起更新,而要先更新跳再更新不跳,因为会出现以下这种情况:
//(i,j)可以从(i-1,j+down[i-1)转移过来,但不能从(i-1,j-up[i-1])转移过来
//如果我们放一起,那么更新连跳的时候的f[i][j-up[i-1]]可能是从上一次下降过来的
//相当于你这一次做的动作是从(i-1,j+down[i-1])下降到(i,j),又从(i,j)上升到(i,j+up[i-1]),显然不合法
		if(bj[i]){//如果这个地方是管子
			for(int j=0;j<=X[i];++j)f[i][j]=1e9;
			for(int j=Y[i];j<=m;++j)f[i][j]=1e9;//这些地方都不能走,还原为正无穷
			int flag=0;
			for(int j=X[i]+1;j<=Y[i]-1;++j)
				if(f[i][j]!=1e9){flag=1;break;}//查找是否有能走的地方
			if(flag)++sum;//sum记录通过的管子数量
			else{printf("0
%d
",sum);return 0;}
		}
		else if(!check(i)){printf("0
%d
",sum);return 0;}
	}
	for(int j=1;j<=m;++j)ans=min(ans,f[n][j]);//找到最小的合法地方
	printf("1
%d
",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11700540.html