题意:\(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\n%d\n",sum);return 0;}
}
else if(!check(i)){printf("0\n%d\n",sum);return 0;}
}
for(int j=1;j<=m;++j)ans=min(ans,f[n][j]);//找到最小的合法地方
printf("1\n%d\n",ans);
return 0;
}