[NOIP2014 提高组] 飞扬的小鸟
题目描述
Flappy Bird
是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
为了简化问题,我们对游戏规则进行了简化和改编:
游戏界面是一个长为
小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
小鸟每个单位时间沿横坐标方向右移的距离为
小鸟高度等于
现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。
输入格式
第
接下来的
接下来
输出格式
共两行。
第一行,包含一个整数,如果可以成功完成游戏,则输出
第二行,包含一个整数,如果第一行为
样例 #1
样例输入 #1
10 10 6 3 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
1 6
样例 #2
样例输入 #2
10 10 4 1 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
0 3
提示
【输入输出样例说明】
如下图所示,蓝色直线表示小鸟的飞行轨迹,红色直线表示管道。
【数据范围】
对于
对于
对于
对于
题解
一道细节比较多的DP题,首先对于小鸟的每一个位置,都可能是从上一个位置降落或者上升得到的,下降只可能下降一次,而上升可以上升多次。
朴素的做法 80pts
我们可以设
因为小鸟到了天花板就不在上升了,我们对天花板高度特殊处理一下就行了
#include <iostream> #define inf 1147483640 #define UP j - up[i - 1] #define DOWN j + down[i - 1] using namespace std; const int maxn = 10005; int n, m, K, f[maxn][1005] = {}, up[maxn] = {}, down[maxn] = {}; int ans = 0; bool wall[maxn][1005] = {}; int p[maxn], l[maxn], h[maxn]; inline void dp() { for (register int i = 1; i <= n; i++) { for (register int j = 1; j <= m - 1; j++) { if (!wall[i][j]) { if (DOWN <= m) // 接近下边界 { f[i][j] = f[i - 1][DOWN]; // cout << "qwq" << f[i - 1][DOWN]; } else f[i][j] = inf; for (int k = 1; j - up[i - 1] * k > 0; k++) { // cout << k << "| "; f[i][j] = min(f[i][j], f[i - 1][j - up[i - 1] * k] + k); } } } if (!wall[i][m]) for (register int j = 1; j <= m; j++) { if (!wall[i - 1][j]) { int k = 1; while (j + up[i - 1] * k < m) k++; f[i][m] = min(f[i][m], f[i - 1][j] + k); } } } } int main() { ios::sync_with_stdio(false); cin >> n >> m >> K; for (register int i = 0; i <= n - 1; i++) cin >> up[i] >> down[i]; for (register int i = 1; i <= n; i++) for (register int j = 0; j <= m; j++) f[i][j] = inf; for (register int i = 1; i <= K; i++) { cin >> p[i] >> l[i] >> h[i]; for (register int j = 0; j <= l[i]; j++) wall[p[i]][j] = 1; for (register int j = h[i]; j <= m; j++) wall[p[i]][j] = 1; } dp(); int min_ans = inf; for (register int i = 1; i <= m; i++) { min_ans = min_ans > f[n][i] ? f[n][i] : min_ans; } if (min_ans >= inf) { cout << "0\n"; int max_ans = 0, done; for (register int i = 1; i <= K; i++) { done = 0; for (register int j = l[i] + 1; j < h[i]; j++) if (f[p[i]][j] < inf) done = 1; if (done) max_ans++; } cout << max_ans; } else { cout << "1\n" << min_ans; } return 0; }
100pts做法
刚才的DP太慢了,刚才对于上升,我们枚举了一个
当高度为
注意,这样做的话,下降和上升不要写在同一个循环里处理,不然无法确定到底是从哪里转移过来的。要写两个循环分开去计算。
这里还有一个坑点,就是这个做法会用到
#include <iostream> #define inf 1147483640 using namespace std; const int maxn = 10005; int n, m, K, f[maxn][2005] = {}, up[maxn] = {}, down[maxn] = {}; int ans = 0, wall[maxn][1005] = {}; int p[maxn], l[maxn], h[maxn]; inline void dp() { for (register int i = 1; i <= n; i++) { for (register int j = up[i - 1] + 1; j <= m + up[i - 1]; j++) if (wall[i][j] != 2) //如果不是水管上半部分 f[i][j] = min(f[i][j - up[i - 1]] + 1, f[i - 1][j - up[i - 1]] + 1); else break; for (register int j = 0; j <= m; j++) //把下部分管道的值删掉 if (wall[i][j]) f[i][j] = inf; for (register int j = 1; j < m; j++) if (!wall[i][j]) if (j + down[i - 1] <= m) // 上一个状态是没单击 f[i][j] = min(f[i - 1][j + down[i - 1]], f[i][j]); if (!wall[i][m]) //处理天花板 for (register int j = 1; j <= up[i - 1]; j++) f[i][m] = min(f[i][m], f[i][m + j]); } } int main() { ios::sync_with_stdio(false); cin >> n >> m >> K; for (register int i = 0; i < n; i++) cin >> up[i] >> down[i]; for (register int i = 1; i <= n; i++) for (register int j = 0; j <= m * 2; j++) f[i][j] = inf; for (register int i = 1; i <= K; i++) { cin >> p[i] >> l[i] >> h[i]; for (register int j = 0; j <= l[i]; j++) wall[p[i]][j] = 1; for (register int j = h[i]; j <= m; j++) wall[p[i]][j] = 2; } dp(); int min_ans = inf; for (register int i = 1; i <= m; i++) min_ans = min(min_ans, f[n][i]); if (min_ans >= inf) { cout << "0\n"; int max_ans = 0, done; for (register int i = 1; i <= K; i++) { done = 0; for (register int j = l[i] + 1; j < h[i]; j++) if (f[p[i]][j] < inf) done = 1; if (done) max_ans++; } cout << max_ans; } else cout << "1\n" << min_ans; return 0; }
这题坑太多了,100pts做法我调了一个晚自习和一个早自习,一开始还以为式子推错了,后来绷不住了去康了一眼题解,才知道水管也要处理
代码写完之后,意识到这个好像就是一个01背包+完全背包。
Comments NOTHING