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题需要步步为营,定义状态、初始状态、状态转移想清楚后方能编写代码。做本题我的思路是通过当前步数退出下一列的步数。

  1. 定义状态
    dp[i][j]: 到(i,j)位置最少步数
  2. 初始状态
    除dp[i][0]以外都初始为+INF
  3. 转移方程
    如果dp[i][j] != INF,hi(i+上升的高度)没碰到管道,则dp[hi][j+1] = min(dp[hi][j+1], dp[i][j]+步数)

注意

  1. 小鸟高度等于 0 或者小鸟碰到管道时,游戏失败。
  2. 小鸟高度为 m 时,无法再上升。
  3. 每一步小鸟可以上升多次。

在本次代码中,做了两处优化:

  1. 当走到某一列无法前进时,提前结束dp过程。
  2. 每次遍历,不考虑管道的位置。

但进官如此,仍然只能得到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);
		}
    }