飞扬的小鸟
题目描述
Flappy Bird 是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。为了简化问题,我们对游戏规则进行了简化和改编:
游戏界面是一个长为n ,高为 m 的二维平面,其中有k 个管道(忽略管道的宽度)。
小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
小鸟每个单位时间沿横坐标方向右移的距离为1 ,竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度X ,每个单位时间可以点击多次,效果叠加;
如果不点击屏幕,小鸟就会下降一定高度Y 。小鸟位于横坐标方向不同位置时,上升的高度X 和下降的高度Y 可能互不相同。
小鸟高度等于0 或者小鸟碰到管道时,游戏失败。小鸟高度为 m 时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以 ,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。
看到这道题思路肯定是用dp了。
看到这道题的思路坑定是dp是没错了。这道题要打满分程序是个很慢的过程 因为有很多细节(考试的时候就不要怎么打了)。
看到这道题我先打了一道非常非常暴力的递推。但是由于关于memset()参数不熟(然而我傻傻的查了5天)。
暴力递推就直接推嘛,up[i]表示第i排点击一次屏幕上升的格子数量,down[i]表示第i排不点下降的格子数。那么得到状态转移方程
dp[i][j]表示第[i][j]个点的最少点击数。
dp[i][j]=min{dp[i-1][j-k*up[i]]+k,dp[i-1][j+down[i]]]}
然后就可以写一个非常暴力的程序
#include<bits/stdc++.h>
using namespace std;
int n,m,k,x2[20000]={0},x1,fn;
int up[10100],down[10100];
bool a[10100][1100]={0};
int f[10100][1100];
int gdown[10010]={0},gup[10010];
inline int read(int &num)
{
num=0;
char c=getchar();
for(;isdigit(c)==0;c=getchar());
for(;isdigit(c)!=0;c=getchar())num=num*10+c-'0';
}
inline void work()
{
for(int i=gdown[1]+1;i<=gup[1]-1;++i)
if(i>=up[1]) f[1][i]=1;
for(int i=gdown[1]+1;i<=gup[1]-1;++i)
if(i<=m-down[1]) f[1][i]=0;
for(int i=2;i<=n;++i)
{
for(int j=gdown[i]+1;j<=gup[i]-1;++j)
{
if(j>=up[i])
{
k=1;
while(j>up[i]*k)
{
if(f[i-1][j-up[i]*k]!=-1)
if(f[i][j]!=-1)
f[i][j]=min(f[i][j],f[i-1][j-up[i]*k]+k);
else f[i][j]=f[i-1][j-up[i]*k]+k;
k++;
}
}
if(j==m)
for(k=m-up[i];k<=gup[i]-1;++k)
{
if(f[i][j]!=-1)
{if(f[i-1][k]!=-1)
f[i][j]=min(f[i][j],f[i-1][k]+1);}
else if(f[i-1][k]!=-1) f[i][j]=f[i-1][k]+1;
if(f[i][j]!=-1)
{if(f[i][k]!=-1)
f[i][j]=min(f[i][j],f[i][k]+1);}
else if(f[i][k]!=-1) f[i][j]=f[i][k]+1;
}
}
for(int j=gdown[i]+1;j<=gup[i]-1;++j)
if(m-j>=down[i])
{
if(f[i-1][j+down[i]]!=-1)
if(f[i][j]!=-1) f[i][j]=min(f[i][j],f[i-1][j+down[i]]);
else f[i][j]=f[i-1][j+down[i]];
}
}
}
inline void init()
{
read(n);read(m);read(k);
fn=k;
for(int i=0;i<=n;++i)
gup[i]=m+1;
for(int i=1;i<=n;++i)
read(up[i]),read(down[i]);
for(int i=1;i<=k;++i)
read(x2[i]),read(gdown[x2[i]]),read(gup[x2[i]]);
}
inline void print()
{
long long min=100000000000000000LL;
for(int i=1;i<=m;++i)
if((f[n][i]<min)&&(f[n][i]!=-1)) min=f[n][i];
if((min!=0)&&(min!=100000000000000000LL)) printf("1\n%d",min);
else {
long long max=0;
for(int i=1;i<=fn;++i)
for(int j=gdown[x2[i]]+1;j<=gup[x2[i]]-1;++j)
if((f[x2[i]][j]!=-1)&&(f[x2[i]][j]!=0)) {max++;break;}
printf("0\n%d",max);
}
}
int main()
{
init();
memset(f,-1,sizeof(f));
work();
print();
return 0;
}
这个暴力程序能拿75分。
要拿满分那就要思考这个状态转移方程怎么改了,自己思考程序,发现上升的处理非常耗时,而这段代码能让我们联想到背包问题中的完全背包。那这道题就可以转化为先是一个上升的完全背包和下降的01背包。每行最顶端再特殊处理即可。
那状态转移方程就变为
dp[i][j]=min{dp[i-1][j-up[i]+1,dp[i][j-up[i]]+1,dp[i-1][j+down[i]]}
特别提醒
这个题目还有一个非常非常非常大的坑。对于任意一行有管道的处理,大部分人肯定是从管道下到管道上做dp的。
这种方法貌似是没有问题的233333
那么让我们来看看这张图
左排的叉叉是左行唯一能到的点,右行被填涂的格子是管道。
假如这一排按一下上升一格,不按显然是要结束游戏的。
那么按照管道跑的话是跑不到,所以在任意一行我们都要把管道的dp【】【】值也要计算出来,在任意一行向另一行跳的时候确保前一行的那个点必须是能到达的点就行了
于是就能写出满分代码了:
#include<bits/stdc++.h>
using namespace std;
int dp[11000][1100],n,m,k,up[10200],down[10200],a[10200];
bool f[11000][1100];
void init()
{
memset(f,1,sizeof(f));
memset(dp,-1,sizeof(dp));
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i)
scanf("%d%d",&up[i],&down[i]);
int nn,xx,yy;
for(int i=1;i<=k;++i)
{
scanf("%d%d%d",&nn,&xx,&yy);
a[i]=nn;
for(int j=0;j<=xx;++j)
f[nn][j]=0;
for(int j=yy;j<=m;++j)
f[nn][j]=0;
}
return;
}
void dp1()
{
for(int j=up[1];j<=m;++j)
if(f[1][j]) dp[1][j]=1;
for(int j=m-down[1];j>=1;--j)
if(f[1][j]) dp[1][j]=0;
for(int x=2;x<=n;++x)
{
for(int i=up[x]+1;i<=m;++i)
{
if(dp[x][i-up[x]]!=-1)
if(dp[x][i]!=-1)
dp[x][i]=min(dp[x][i],dp[x][i-up[x]]+1);
else dp[x][i]=dp[x][i-up[x]]+1;
if(f[x-1][i-up[x]]&&(dp[x-1][i-up[x]]!=-1))
if(dp[x][i]!=-1)
dp[x][i]=min(dp[x][i],dp[x-1][i-up[x]]+1);
else dp[x][i]=dp[x-1][i-up[x]]+1;
}
if(f[x][m])
{
for(int i=m-up[x]+1;i<=m;++i)
{
if(dp[x][i]!=-1)
if(dp[x][m]!=-1)
dp[x][m]=min(dp[x][m],dp[x][i]+1);
else dp[x][m]=dp[x][i]+1;
if(dp[x-1][i]!=-1&&f[x-1][i])
if(dp[x][m]!=-1)
dp[x][m]=min(dp[x][m],dp[x-1][i]+1);
else dp[x][m]=dp[x-1][i]+1;
}
}
for(int i=1;i<=m-down[x];++i)
if(f[x][i]&&f[x-1][i+down[x]]&&(dp[x-1][i+down[x]]!=-1))
if(dp[x][i]!=-1)
dp[x][i]=min(dp[x][i],dp[x-1][i+down[x]]);
else
dp[x][i]=dp[x-1][i+down[x]];
}
return;
}
void print()
{
bool flag=false;
int Min=1000000000;
for(int i=1;i<=m;++i)
if(dp[n][i]!=-1)
Min=min(Min,dp[n][i]);
if(Min!=1000000000) {printf("1\n%d",Min);return;}
int ans=0;
for(int i=1;i<=k;++i)
{
for(int j=1;j<=m;++j)
if(f[a[i]][j]&&dp[a[i]][j]!=-1)
{ans++;break;}
}
printf("0\n%d",ans);
return;
}
int main()
{
init();
dp1();
print();
return 0;
}