飞扬的小鸟

来源:互联网 发布:青山知可子女机械人511 编辑:程序博客网 时间:2024/05/16 09:36

【问题描述】

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

【输入】

输入文件名 bird.in。 第 1 行有 3 个整数 n,m,k,分别表示游戏界面的长度,高度和水管的数量,每两个整数之间用一个空格隔开; 接下来的 n 行,每行 2 个用一个空格隔开的整数 X 和 Y,依次表示在横坐标位置 0~n-1 上玩家点击屏幕后,小鸟在下一位置上升的高度 X,以及在这个位置上玩家不点击屏幕时, 小鸟在下一位置下降的高度 Y。 接下来 k 行,每行3个整数 P,L,H,每两个整数之间用一个空格隔开。每行表示一 个管道,其中 P 表示管道的横坐标,L 表示此管道缝隙的下边沿高度为L,H表示管道缝隙 上边沿的高度(输入数据保证 P 各不相同,但不保证按照大小顺序给出)。

【输出】

输出文件名为 bird.out。 共两行。
第一行,包含一个整数,如果可以成功完成游戏,则输出 1,否则输出 0。 第二行,包含一个整数,如果第一行为1,则输出成功完成游戏需要最少点击屏幕数, 否则,输出小鸟最多可以通过多少个管道缝隙。

这是一道NOIP的真题,作为第三题的话难度是不算大的,分也比较好切。

题目分析:
关键字:DP,取或不取以及取多少(背包问题)
核心还是多阶段决策:对于现在这个位置而言,决策有两种
1. 不点击屏幕(即会向下掉)
2. 点击屏幕(即向上飞)

若点击屏幕,还需要考虑点击多少下,由于可以无限制点击,就可以想到完全背包问题。同时的,我们知道现在的决策,我们就可以去更新下一个状态,
那么转移也只需要从前一次的状态转移过来即可,dp的定义也十分自然的得出:dp[i][j]表示达到i列的j位置时,最小点击次数是多少。
状态转移方程:
dp[i][j]=min(dp[i][j],dp[i-1][j-k*up[i-1]],dp[i-1][j+down[i-1]]|j-k*up[i-1]>0&&j+down[i-1]<=m);(注意:若j是顶端m是,可以从m-up[i-1]到m转移过来)
//i是目前的横坐标,j是纵坐标,k是点击次数

若不加优化的话,就是直接循环k点击的次数,规规矩矩地更新,肯定是超时的,那我们看看他们为什么会超时:

这里写图片描述

如图所示,目前更新的点事由1,2,3,4更新过来的,而之前更新的点事由1,2,3更新过来的,发现红线有的都与黑线平行,说明它们的更新只相差4这个点,而其它的点都在之前更新的点更新过了,又重复计算了1,2,3三个点,所以超时了,既然在之前的点更新过了,我们就可以从前面的点直接转移过来。修改状态转移方程(向上飞时的方程):
dp[i][j]=min{dp[i][j-up[i-1]]+1,dp[i-1][j-up[i-1]]+1}
这样k这个维度就节省下来了,复杂度为O(n*m),就可以过了。

其实细心的读者可以发现其中的背包,我们可以把每个概念抽象:

  1. 点击次数为价值。
  2. 上升下降的空间为体积。
  3. 每次上升下降的距离为每个物品的体积。
  4. 每个横坐标是一件物品。

题目就变成求最小价值,看看是否好理解呢.下面给出代码:
比较标准的两份(第二份是Komachi大犇的):

#include<cstdio>#include<queue>#include<cmath>#include<iostream>#include<algorithm>#include<cstring>using namespace std;#define N_MAX 10005#define M_MAX 1005#define fin(x) freopen(x,"r",stdin)#define fout(x) freopen(x,"w",stdout)int n,m,K,INF,ans=1E9;int dp[N_MAX][M_MAX],up[N_MAX],down[N_MAX],L[N_MAX],H[N_MAX];//dp[i][j]表示从开始到达第i个点的j高度最少点击屏幕的次数void chkmin(int &a,int b){if(a>b) a=b;}void chkmax(int &a,int b){if(a<b) a=b;}int main(){    scanf("%d %d %d",&n,&m,&K);    for(int i=0;i<n;i++) scanf("%d %d",&up[i],&down[i]);    for(int i=0;i<=n;i++) L[i]=1,H[i]=m;    for(int i=1;i<=K;i++){        int x,y,z;        scanf("%d %d %d",&x,&y,&z);        L[x]=y+1,H[x]=z-1;    }    memset(dp,63,sizeof(dp));INF=dp[1][1];//初始化     for(int i=1;i<=m;i++) dp[0][i]=0;//第0列都可以出发,所以为0     int res=0;//res目前通过的管道数     for(int i=1;i<=n;i++){        int flag=0;        //按         for(int j=1;j<=m;j++){            if(j-up[i-1]>=1){//类似完全背包                 chkmin(dp[i][j],dp[i][j-up[i-1]]+1);                chkmin(dp[i][j],dp[i-1][j-up[i-1]]+1);            }        }        for(int j=m-up[i-1];j<=m;j++){//可能会顶到,特别判断一下             chkmin(dp[i][m],dp[i][j]+1);            chkmin(dp[i][m],dp[i-1][j]+1);        }        //不按        for(int j=L[i];j<=H[i];j++) if(j+down[i-1]<=m) chkmin(dp[i][j],dp[i-1][j+down[i-1]]);        for(int j=1;j<L[i];j++) dp[i][j]=INF;//把不行的赋值会INF         for(int j=H[i]+1;j<=m;j++) dp[i][j]=INF;        for(int j=1;j<=m;j++) if(dp[i][j]!=INF){flag=1;break;}        if(!flag){            printf("0\n%d\n",res);            return 0;           }else{            if(H[i]!=m) res++;        }    }    int ans=INF;    for(int i=1;i<=m;i++) chkmin(ans,dp[n][i]);    printf("1\n%d\n",ans);    return 0;}#include<bits/stdc++.h>using namespace std;#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)void Rd(int &res){char c;res=0;while(c=getchar(),c<48);do res=(res<<3)+(res<<1)+(c^48);while(c=getchar(),c>47);}#define N 10004#define M 1004#define INF 0x3f3f3f3f#define chkmin(a,b) a=min(a,b)int n,m,k,X[N],Y[N],L[N],H[N];struct P100 {int DP[N][M],V[M];void Solve() {memset(DP,63,sizeof(DP));memset(DP[0],0,sizeof(DP[0]));REP(j,0,L[0]+1)DP[0][j]=INF;REP(j,H[0],m+1)DP[0][j]=INF;REP(i,1,n+1){REP(j,0,m){if(j>=X[i])V[j]=min(DP[i-1][j]+1,V[j-X[i]]+1);else V[j]=DP[i-1][j]+1;V[j]=min(INF,V[j]);}REP(j,0,m){if(j+Y[i]<=m) DP[i][j]=DP[i-1][j+Y[i]];if(j>=X[i]) chkmin(DP[i][j],V[j-X[i]]);}chkmin(DP[i][m],DP[i-1][m]+1);REP(j,1,m)chkmin(DP[i][m],DP[i-1][j]+(m-j+X[i]-1)/X[i]);REP(j,0,L[i]+1) DP[i][j]=INF;REP(j,H[i],m+1) DP[i][j]=INF;}int Ans=INF;REP(j,1,m+1) chkmin(Ans,DP[n][j]);if(Ans<INF){puts("1");printf("%d\n",Ans);return;}puts("0");k-=H[n]!=m+1;DREP(i,n-1,0) {REP(j,1,m+1)if(DP[i][j]<INF) {printf("%d\n",k);return;}if(H[i]!=m+1) k--;}}}P100;int main(){Rd(n),Rd(m),Rd(k);REP(i,1,n+1) Rd(X[i]),Rd(Y[i]);REP(i,0,n+2) L[i]=0,H[i]=m+1;REP(i,0,k){int x,l,h;Rd(x),Rd(l),Rd(h);L[x]=l,H[x]=h;}P100.Solve();return 0;}

(顺便加上空间优化)(同学那copy的)

#include<bits/stdc++.h>#define M 10005using namespace std;struct node1{int up,down;}A[M];struct node2{int l,h,p;node2(){p=0;}}B[M];int dp[2][1005];int main(){    int n,m,k;    scanf("%d%d%d",&n,&m,&k);    for(int i=1;i<=n;i++)scanf("%d%d",&A[i].up,&A[i].down);    for(int i=1;i<=k;i++){        int x;scanf("%d",&x);        scanf("%d%d",&B[x].l,&B[x].h);B[x].p=1;    }    for(int i=1;i<=n;i++){        memset(dp[i%2],63,sizeof(dp[i%2]));        int L,R,p=1;        if(B[i].p)L=B[i].l+1,R=B[i].h-1;        else L=1,R=m;        for(int j=1;j<=m;j++){            int d=j+A[i].up;            if(d>m)d=m;            if(dp[i%2][d]>dp[1-i%2][j]+1)dp[i%2][d]=dp[1-i%2][j]+1;        }        for(int j=1;j<=m;j++){            int d=j+A[i].up;            if(d>m)d=m;            if(dp[i%2][d]>dp[i%2][j]+1)dp[i%2][d]=dp[i%2][j]+1;        }        for(int j=1;j<=m;j++)            if(j-A[i].down>=L&&j-A[i].down<=R&&dp[i%2][j-A[i].down]>dp[1-i%2][j]) dp[i%2][j-A[i].down]=dp[1-i%2][j];        for(int j=1;j<=m;j++){            if(dp[i%2][j]<=10000000&&j<=R&&j>=L) p=0;            else dp[i%2][j]=10000005;        }        if(p){            int cnt=0;            for(int j=0;j<i;j++)if(B[j].p)cnt++;            printf("0\n%d\n",cnt);            return 0;         }    }    int ans=1e9;    for(int i=1;i<=m;i++)if(ans>dp[n%2][i])ans=dp[n%2][i];    printf("1\n%d\n",ans);    return 0;}
原创粉丝点击