P1941 NOIP2014 提高組 飛揚的小鳥 題解

jxyanglinus發表於2024-10-16

P1941 NOIP2014 提高組 飛揚的小鳥

分析

揹包經典演變問題玩得挺花

\(f[i][j]\) 表示 到達 \((i, j)\) 的時候的最小點選次數。題目中對於每一個 \(i\) 有兩種處理:點選與不點選(重點:點選可以疊加)。所以,對於點選,我們可以像完全揹包一樣轉移,而不點選就按照 01 揹包轉移。

對於管道,我們把管道的 \(f[i][j]\) 設為 \(\inf\) 表示無法到達。

點選之後高度可能超過 \(m\),但是題目說了最高到 \(m\),所以超過 \(m\) 的一律當在 \(m\) 高度處理。

程式碼裡有詳細說明。

程式碼 | 解析

// Momoka!!!
#include <bits/stdc++.h>
using namespace std;

const int maxn = 10005;
const int maxm = 2005;
int n, m, k;
int up[maxn], down[maxn];
int low[maxn], high[maxn];
// f[i][j]:到達 (i, j) 時的最小點選次數。
int f[maxn][maxm]; // 可選擇點選與不點選,點選可以疊加;點選按照完全揹包轉移,不點選按照 01 揹包轉移。
bool isPipe[maxn];

int main() {
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &up[i], &down[i]);
	}
	for (int i = 1; i <= k; i++) {
		int p, l, h;
		scanf("%d%d%d", &p, &l, &h);
		isPipe[p] = true, low[p] = l, high[p] = h;
	}
	memset(f, 0x3f, sizeof(f));
	for (int i = 1; i <= m; i++) f[0][i] = 0;
	for (int i = 1; i <= n; i++) {
		// 向上(點選)按照完全揹包轉移。
		for (int j = up[i] + 1; j <= m + up[i]; j++) {
			f[i][j] = min(f[i - 1][j - up[i]] + 1, f[i][j - up[i]] + 1);
		}
		// 點選之後向上飛可能超過 m,但是題目說了最高飛到 m,可以一直貼著最頂上飛。
		for (int j = m + 1; j <= m + up[i]; j++) {
			f[i][m] = min(f[i][j], f[i][m]); // 所以處理一下。
		}
		// 向下(不點選)按照 01 揹包轉移
		for (int j = 1; j <= m - down[i]; j++) {
			// 為什麼 01 揹包不從大到小列舉?
			// 因為用 f[i-1][j+down[i]] 來轉移,所以從 1 到 m-down[i] 才是 01 揹包。
			f[i][j] = min(f[i][j], f[i - 1][j + down[i]]);
		}
		// 剛才把 i 列當作沒有管道(障礙)了,若 i 列是管道,則需要消除影響。
		if (isPipe[i]) {
			for (int j = 1; j <= low[i]; j++) {
				f[i][j] = 0x3f3f3f3f;
			}
			for (int j = high[i]; j <= m; j++) {
				f[i][j] = 0x3f3f3f3f;
			}
		}
	}
	int ans = 0x3f3f3f3f;
	for (int i = 1; i <= m; i++) {
		ans = min(ans, f[n][i]);
	}
	if (ans < 0x3f3f3f3f) {
		printf("1\n%d", ans);
	} else {
		// 無法完成遊戲。
		ans = 0;
		bool flag = true;
		int endpos = 0;
		for (int i = n; i >= 1; i--) {
			for (int j = 1; j <= m; j++) {
				if (f[i][j] < 0x3f3f3f3f) {
					flag = false;
					endpos = i; // 最後可以到達的地方。
					break;
				}
			}
			if (!flag) break;
		}
		// 統計所有經過的管道。
		for (int i = 1; i <= endpos; i++) {
			if (isPipe[i]) ans++;
		}
		printf("0\n%d", ans);
	}
	return 0;
}

相關文章