题意:(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;
}