2022.03.21飞扬的小鸟
题目描述
Flappy Bird 是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
为了简化问题,我们对游戏规则进行了简化和改编:
游戏界面是一个长为 n,高为 m 的二维平面,其中有 k 个管道(忽略管道的宽度)。
小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
小鸟每个单位时间沿横坐标方向右移的距离为 1,竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度 x,每个单位时间可以点击多次,效果叠加;如果不点击屏幕,小鸟就会下降一定高度 y。小鸟位于横坐标方向不同位置时,上升的高度 x 和下降的高度 y 可能互不相同。
小鸟高度等于 0 或者小鸟碰到管道时,游戏失败。小鸟高度为 m 时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。
输入格式
第 1 行有 3 个整数 n, m, k, 分别表示游戏界面的长度,高度和水管的数量,每两个整数之间用一个空格隔开;
接下来的 n 行,每行 2 个用一个空格隔开的整数 x 和 y,依次表示在横坐标位置 0∼n−1 上玩家点击屏幕后,小鸟在下一位置上升的高度 x,以及在这个位置上玩家不点击屏幕时,小鸟在下一位置下降的高度 y。
接下来 k 行,每行 3 个整数 p,l,h,每两个整数之间用一个空格隔开。每行表示一个管道,其中 p 表示管道的横坐标,l 表示此管道缝隙的下边沿高度,h 表示管道缝隙上边沿的高度(输入数据保证 p 各不相同,但不保证按照大小顺序给出)。
输入格式
第 1 行有 3 个整数 n,m,k,分别表示游戏界面的长度,高度和水管的数量,每两个整数之间用一个空格隔开;
接下来的 n 行,每行 2 个用一个空格隔开的整数 x 和 y,依次表示在横坐标位置 0∼n−1 上玩家点击屏幕后,小鸟在下一位置上升的高度 x,以及在这个位置上玩家不点击屏幕时,小鸟在下一位置下降的高度 y。
接下来 k 行,每行 3 个整数 p,l,h,每两个整数之间用一个空格隔开。每行表示一个管道,其中 p 表示管道的横坐标,l 表示此管道缝隙的下边沿高度,h 表示管道缝隙上边沿的高度(输入数据保证 p 各不相同,但不保证按照大小顺序给出)。
输出格式
共两行。
第一行,包含一个整数,如果可以成功完成游戏,则输出 1,否则输出 0。
第二行,包含一个整数,如果第一行为 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
6
思路
做dp题需要步步为营,定义状态、初始状态、状态转移想清楚后方能编写代码。做本题我的思路是通过当前步数退出下一列的步数。
- 定义状态
dp[i][j]: 到(i,j)位置最少步数 - 初始状态
除dp[i][0]以外都初始为+INF - 转移方程
如果dp[i][j] != INF,hi(i+上升的高度)没碰到管道,则dp[hi][j+1] = min(dp[hi][j+1], dp[i][j]+步数)
注意
- 小鸟高度等于 0 或者小鸟碰到管道时,游戏失败。
- 小鸟高度为 m 时,无法再上升。
- 每一步小鸟可以上升多次。
在本次代码中,做了两处优化:
- 当走到某一列无法前进时,提前结束dp过程。
- 每次遍历,不考虑管道的位置。
但进官如此,仍然只能得到80分…
代码
int n,m, k, INF=Integer.MAX_VALUE;
int[] x = new int[10001];
int[] y = new int[10001];
pip[] ps = new pip[10001];
int[][] dp = new int[1002][10002];
//dp[i][j]: 到(i,j)位置最少步数
//初始化状态:除dp[i][0]以外都初始为+INF
//转移方程:如果dp[i][j] != INF,hi或li有一个在范围内,
void test() throws IOException {
Reader cin = new Reader();
n = cin.nextInt();
m = cin.nextInt();
k = cin.nextInt();
for(int i = 0; i < n; i++) {
x[i] = cin.nextInt();
y[i] = cin.nextInt();
}
for(int i = 0; i < k; i++) {
int co = cin.nextInt();
int lo = cin.nextInt();
int hi = cin.nextInt();
ps[co] = new pip(lo, hi);
}
for(int i = 0; i <= m ;i++) for(int j = 1; j <= n; j++) dp[i][j] = INF;
int lasInd = -1;
for(int j = 0; j < n; j++) {
boolean canGo = false;
int beg = 1, end = m;
if(ps[j] != null) {
beg = ps[j].low+1;
end = ps[j].high-1;
}
for(int i = beg; i <= end; i++) {
if(dp[i][j] == INF) continue;
int curx = 1;
while(true) {
int hi = Math.min(m, i+curx*x[j]);
if(ps[j+1]==null || (ps[j+1].low < hi && ps[j+1].high > hi)) { //如果(i,j)可到达
//更新dp[hi][j+1]
dp[hi][j+1] = Math.min(dp[hi][j+1], dp[i][j]+curx);
canGo = true;
}
if(hi >= m) break;
curx++;
}
int li = i-y[j];
if(li > 0 && (ps[j+1]==null || (ps[j+1].low < li && ps[j+1].high > li))) { //如果(i,j)可到达
//更新dp[hi][j+1]
dp[li][j+1] = Math.min(dp[li][j+1], dp[i][j]);
canGo = true;
}
}
if(!canGo) { //第j+1不可达
lasInd = j;
break;
}
}
int res = INF;
for(int i = 0; i <= m; i++) {
res = Math.min(dp[i][n], res);
}
if(res == INF) { //如果不可通
int cnt = 0;
for(int j = 0; j <= lasInd; j++) {
if(ps[j] != null) cnt++;
}
System.out.println(0);
System.out.println(cnt);
} else {
System.out.println(1);
System.out.println(res);
}
}