洛谷 P1941 飞扬的小鸟 【DP+众多特判】
题目:
为了简化问题,我们对游戏规则进行了简化和改编:
-
游戏界面是一个长为n ,高为 m 的二维平面,其中有k 个管道(忽略管道的宽度)。
-
小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
- 小鸟每个单位时间沿横坐标方向右移的距离为1 ,竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度X ,每个单位时间可以点击多次,效果叠加;
如果不点击屏幕,小鸟就会下降一定高度Y 。小鸟位于横坐标方向不同位置时,上升的高度X 和下降的高度Y 可能互不相同。
- 小鸟高度等于0 或者小鸟碰到管道时,游戏失败。小鸟高度为 m 时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以 ,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。
分析:
这题显然是DP,用f[i][j]表示小鸟飞到横坐标 i ,纵坐标 j 时所需要点击的最少次数。可以分以下三类状态转移:
1、这一秒的状态是由上一秒上跳得到的:(注意:这是一个完全背包问题,因此在没有压维时,需要注意细节(红色))
f[i][j]=min(f[i][j],f[i-1][j-hop[i]]+1);
f[i][j]=min(f[i][j],f[i][j-hop[i]]+1);
2、这一秒的状态是由上一秒上跳撞到天花板而不再上升得到的:(可以附在上一个状态中经j==m特判处理)
f[i][j]=min(f[i][j],f[i-1][p]);
f[i][j]=min(f[i][j],f[i][p]); (m-hop[i]<=p<=m)从这些点开始不论点几次,小鸟总在天花板上
3、这一秒的状态是由上一秒下掉得到的:(注意:一秒只能掉一次,因此掉是0/1背包)
f[i][j]=min(f[i][j],f[i-1][j+drop[i]]);
最后,再处理在管道中的那些不合法的点:
f[i][j]=inf; (j<=tube[i].low) 或 (j>=tube[i].high)
还要注意一定要先处理上升再处理下降,因为上升用到了f[i][j-hop[i]],有可能被下降时更新过,一个鸟不能在一秒内既上升又下降。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define inf 999999999 using namespace std; struct point { int low,high; }tube[10001]; int m,n,k; int x[10001],y[10001]; int f[10001][1001]; int main() { cin>>n>>m>>k; for(int i=0;i<n;i++) { cin>>x[i]>>y[i]; tube[i].high=m+1; tube[i].low=0; } tube[n].high=m+1; tube[n].low=0; for(int i=1;i<=k;i++) { int p; cin>>p; cin>>tube[p].low>>tube[p].high; } memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) f[i][j]=inf; //init(); f[0][0]=inf; for(int i=1;i<=n;i++) { int hh=x[i-1],dd=y[i-1]; int ll=tube[i].low,tt=tube[i].high; //UP for(int j=hh;j<=m;j++) { f[i][j]=min(f[i][j],f[i-1][j-hh]+1); f[i][j]=min(f[i][j],f[i][j-hh]+1); if(j==m) for(int p=m-hh;p<=m;p++) { f[i][m]=min(f[i][m],f[i-1][p]+1); f[i][m]=min(f[i][m],f[i][p]+1); } } //DOWN for(int j=ll+1;j<tt;j++) if(j+dd<=m) f[i][j]=min(f[i][j],f[i-1][j+dd]); for(int j=tt;j<=m;j++) f[i][j]=inf; for(int j=1;j<=ll;j++) f[i][j]=inf; } int ans=inf,cnt=k; for(int i=n;i>=1;i--) { for(int j=tube[i].low+1;j<tube[i].high;j++) ans=min(ans,f[i][j]); if(ans<inf) break; if(tube[i].high<=m) cnt--; } if(cnt==k) cout<<1<<endl<<ans; else cout<<0<<endl<<cnt; return 0; }