Noip2014 Day1 T3 飞扬的小鸟(Dp)
2017-08-14 17:42
465 查看
题目描述
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 ,则输出成功完成游戏需要最少点击屏幕数,否则,输出小鸟最多可以通过多少个管道缝隙。
输入输出样例
输入样例#1:
10 10 63 9
9 9
1 2
1 3
1 2
1 1
2 1
2 1
1 6
2 2
1 2 7
5 1 5
6 3 5
7 5 8
8 7 9
9 1 3
输出样例#1:
16
输入样例#2:
10 10 41 2
3 1
2 2
1 8
1 8
3 2
2 1
2 1
2 2
1 2
1 0 2
6 7 9
9 1 4
3 8 10
输出样例#2:
03
说明
【输入输出样例说明】如下图所示,蓝色直线表示小鸟的飞行轨迹,红色直线表示管道。
【数据范围】
对于30% 的数据:5 ≤ n ≤ 10,5 ≤ m ≤ 10,k = 0 ,保证存在一组最优解使得同一单位时间最多点击屏幕3 次;
对于50% 的数据:5 ≤ n ≤ 2 0 ,5 ≤ m ≤ 10,保证存在一组最优解使得同一单位时间最多点击屏幕3 次;
对于70% 的数据:5 ≤ n ≤ 1000,5 ≤ m ≤ 1 0 0 ;
对于100%的数据:5 ≤ n ≤ 100 0 0 ,5 ≤ m ≤ 1 0 00,0 ≤ k < n ,0
思路(借鉴 xm dalao的PPT)
对于每一个非管道上的坐标,设为(x,y),那么枚举横坐标为x-1的点,如果能从这个点上来,那么状态转移方程就是:
if(j>k&&(j-k)%up[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]);
(纵坐标为j,横坐标为x-1的点纵坐标为k)
从上面掉下来的状态转移方程更好写,找到(x,y+down[i-1]),写个状态转移方程:
if(k-down[i-1]==j)f[i][j]=min(f[i][j],f[i-1][k]); (这个很好理解,我不多说了)
然后因为跳到m边界后就跳不上去了,所以要对这个进行特判
if(k==m&&down[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]);
else if(k==m&&down[i-1]!=0)f[i][j]=min(f[i][j],f[i-1][k]+1);
else {if((j-k)%up[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]);
else f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]+1);}
但是这样的话就会超时,最多也就只能拿70分,当然写得好可以拿75。
这里的优化是个难点,很难想。就是每一个点它的答案,只能由下一个推得。
举个例子,从第3列开始跳,一步跳3格。(3,5)这一格的dp[3][5]=0,那么(4,11)怎么求呢?
dp[4][8]=min(dp[4][8],min(dp[3][5],dp[4][5])+1)=1
dp[4][11]=min(dp[4][11],min(dp[3][8],dp[4][8])+1)=2 状态转移方程:
dp[i][j]=min(dp[i][j],dp[i-1][j-up[i-1]]+1);
dp[i][j]=min(dp[i][j],dp[i][j-up[i-1]]+1);
掉和特判跟前面一样,不过注意一点:上升得写在下降之前,不然的话上升的有可能会由下降推得,max=80分!
这样,就把O(nm^2)降到了O(nm),100分程序就写成了!
代码之70分版本(不是自己写的)
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int maxn=10000+10,maxm=1000+10; //写起来方便 int n,m,i,j,k,pipe,f[maxn][maxm],x; struct apipe{int downpos,uppos;}; int up[maxn],down[maxn]; apipe p[maxn]; void readdata() //读入数据,初始化 { scanf("%d %d %d",&n,&m,&pipe); for(i=0;i<n;i++)scanf("%d %d",&up[i],&down[i]); for(i=0;i<=n;i++){p[i].downpos=-1; p[i].uppos=m+1;} for(i=1;i<=pipe;i++){scanf("%d",&x); scanf("%d %d",&p[x].downpos,&p[x].uppos);} for(i=0;i<=m;i++)f[0][i]=0; //第一排任意高度起跳 } bool check(int x,int y) //再不在管道上 { if(p[x].downpos<y&&p[x].uppos>y)return true; return false; } void work() //主过程 { for(i=1;i<=n;i++) for(j=1;j<=m;j++) for(k=1;k<=m;k++) if(check(i,j)&&check(i-1,k)) if(j!=m) //不在管道上 { if(k-down[i-1]==j)f[i][j]=min(f[i][j],f[i-1][k]); //掉下去 if(j>k&&(j-k)%up[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]); //跳上去 } else //j=m特判 { if(k==m&&down[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]); else if(k==m&&down[i-1]!=0)f[i][j]=min(f[i][j],f[i-1][k]+1); else {if((j-k)%up[i-1]==0)f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]); else f[i][j]=min(f[i][j],f[i-1][k]+(j-k)/up[i-1]+1);} } } int findpipe(int num) //从0到num-1有几根管道 { int tot=0; for(k=0;k<num;k++) if(p[k].uppos!=m+1)tot++; return tot; } void find() //找答案 { bool flag; int minimum=1000000000; for(i=1;i<=n;i++) { flag=false; for(j=1;j<=m;j++)flag|=f[i][j]<10000000; if(!flag){cout<<0<<endl; cout<<findpipe(i)<<endl; break;} } if(flag) {for(j=1;j<=m;j++)minimum=min(minimum,f [j]); cout<<1<<endl; cout<<minimum<<endl;} } int main() { memset(f,sizeof(f),10000000); readdata(); work(); find(); return 0; }
满分代码
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int M=10005,N=1005,Inf=0x3f3f3f3f; int topp[M],endd[M],have[M]; int dp[M] ; int up[M],down[M]; int n,m,k,maxx; 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++) topp[i]=m+1; for(int i=1;i<=k;i++){ int x;scanf("%d",&x); scanf("%d%d",&endd[x],&topp[x]);have[x]=1; //记录有没有管道 } for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) dp[i][j]=Inf; dp[0][0]=Inf; //初始化 for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++)//上升 { if(j>up[i-1]) { dp[i][j]=min(dp[i][j],dp[i-1][j-up[i-1]]+1); dp[i][j]=min(dp[i][j],dp[i][j-up[i-1]]+1); } if(j==m)//特判 { for(int d=m-up[i-1];d<=m;d++) { dp[i][j]=min(dp[i][j],dp[i-1][d]+1); dp[i][j]=min(dp[i][j],dp[i][d]+1); } } } for(int j=endd[i]+1;j<=topp[i]-1;j++)//下降 { if(j+down[i-1]<=m) dp[i][j]=min(dp[i][j],dp[i-1][j+down[i-1]]); } for(int j=1;j<=endd[i];j++) dp[i][j]=Inf; for(int j=topp[i];j<=m;j++) dp[i][j]=Inf; } int ans=Inf,tot=k; //找答案 for(int i=n;i>=1;i--) { for(int j=endd[i]+1;j<=topp[i]-1;j++) ans=min(ans,dp[i][j]); if(ans!=Inf) break; if(have[i]) tot--; } if(tot==k) printf("1\n%d\n",ans); else printf("0\n%d\n",tot); return 0; }
相关文章推荐
- 【NOIP 2014 DAY1 T3】飞扬的小鸟(DP)
- 【NOIP 2014 day1 T3】飞扬的小鸟——题解
- NOIP 2014 Day1 T3飞扬的小鸟
- 【NOIP2014 Day1 T3】飞扬的小鸟
- NOIP 2014 Day1 T3飞扬的小鸟
- NOIP2014 Day1 P3 飞扬的小鸟
- 【NOIp 2014】【二维dp】飞扬的小鸟
- 【NOIP2014】 飞扬的小鸟 完全背包dp
- NOIp 2014飞扬的小鸟【dp】By cellur925
- UOJ #17. 【NOIP2014】飞扬的小鸟 背包DP
- NOIP2014 飞扬的小鸟 解题报告(DP)
- 【NOIP2014提高组T3】飞扬的小鸟-完全背包
- NOIP2014 飞扬的小鸟 (DP)
- noip2014 day1-3 飞扬的小鸟
- NOIP 2014 飞扬的小鸟 [DP]
- NOIP2014复赛提高组day1(A:生活大爆炸版石头剪刀布 B:联合权值 C:飞扬的小鸟)
- NOIP 2014 day1第三题 飞扬的小鸟
- 【NOIP2014】飞扬的小鸟 背包dp
- NOIP2014 Day1T3 飞扬的小鸟 dp
- NOIP2014飞扬的小鸟[DP][WRONG]