P2196 [NOIP 1996 提高组] 挖地雷
题目描述
在一个地图上有 $N\ (N \le 20)$ 个地窖,每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后每次可以移动到一个编号比当前节点大且联通的节点去挖地雷,当无满足条件的节点时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。
输入格式
有若干行。
第 $1$ 行只有一个数字,表示地窖的个数 $N$。
第 $2$ 行有 $N$ 个数,分别表示每个地窖中的地雷个数。
第 $3$ 行至第 $N+1$ 行表示地窖之间的连接情况:
第 $3$ 行有 $n-1$ 个数($0$ 或 $1$),表示第一个地窖至第 $2$ 个、第 $3$ 个 $\dots$ 第 $n$ 个地窖有否路径连接。如第 $3$ 行为 $1\space 1\space 0\space 0\space 0\cdots 0$,则表示第 $1$ 个地窖至第 $2$ 个地窖有路径,至第 $3$ 个地窖有路径,至第 $4$ 个地窖、第 $5$ 个 $\dots$ 第 $n$ 个地窖没有路径。
第 $4$ 行有 $n-2$ 个数,表示第二个地窖至第 $3$ 个、第 $4$ 个 $\dots$ 第 $n$ 个地窖有否路径连接。
……
第 $n+1$ 行有 $1$ 个数,表示第 $n-1$ 个地窖至第 $n$ 个地窖有否路径连接。(为 $0$ 表示没有路径,为 $1$ 表示有路径)。
输出格式
第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。
第二行只有一个数,表示能挖到的最多地雷数。
输入输出样例 #1
输入 #1
1 2 3 4 5 6
| 5 10 8 4 7 6 1 1 1 0 0 0 0 1 1 1
|
输出 #1
说明/提示
【样例解释】
最优路径为 $1 \to 3 \to 4 \to 5$,结果为 $27$。
【题目来源】
NOIP 1996 提高组第三题。
思路分析 + 代码实现
地窖之间依据编号(从小到大)构成一个 DAG ,相当于一个在有向无环图(DAG)上求最长路径的问题,每个节点有正权值(地雷数)。所以一开始想到的是拓扑排序,用 BFS 解决,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <bits/stdc++.h> using namespace std; constexpr int MAXN = 25; vector<int> G[MAXN]; int a[MAXN]; struct State{ int u, cnt; vector<int> rec; }; int main(){ int N, ans = 0; cin >> N; for(int i = 1; i <= N; i++) cin >> a[i]; for(int t, i = 1; i < N; i++) for(int j = i + 1; j <= N; j++) { cin >> t; if (t) G[i].push_back(j); } vector<int> ans_rec; queue<State> Q; for(int i = 1; i <= N; i++) { State t = {i, a[i]}; t.rec.push_back(i); Q.push(t); } while (!Q.empty()) { State now = Q.front(); Q.pop(); if (now.cnt > ans) { ans = now.cnt; ans_rec.clear(); ans_rec = now.rec; } for(int v : G[now.u]) { State t = now; t.u = v; t.rec.push_back(v); t.cnt += a[v]; Q.push(t); } } for(int i : ans_rec) cout << i << " "; cout << "\n" << ans; return 0; }
|
当然也可以用记忆化搜索,时间复杂度 $O(n^2)$ ,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <bits/stdc++.h> using namespace std; constexpr int MAXN = 25; bool connected[MAXN][MAXN]; int a[MAXN], dp[MAXN], N; int dfs(int x) { if (dp[x]) return dp[x]; int t = 0; for(int i = x + 1; i <= N; i++) if (connected[x][i]) t = max(t, dfs(i)); return dp[x] = a[x] + t; } int main() { cin >> N; for(int i = 1; i <= N; i++) cin >> a[i]; for(int i = 1; i < N; i++) for(int j = i + 1; j <= N; j++) cin >> connected[i][j]; int ans = 0; for(int i = 1; i <= N; i++) ans = max(ans, dfs(i)); for(int i = 1; i <= N; i++) if (dp[i] == ans) { cout << i << " "; for(int j = i + 1; j <= N; j++) if (connected[i][j] && dp[i] == a[i] + dp[j]) cout << (i = j) << " "; } cout << "\n" << ans; return 0; }
|
注意根据答案推路径的细节。
当然, DP 更加简洁,设 $dp[i]$ 表示以地窖 $i$ 为起点能挖到的最大地雷数。
对于每个地窖 $i$,考虑它所有能到达的后续节点 $j$(满足 $j > i$ 且 $connected[i][j] = 1$)则状态转移方程为:
DP 部分代码如下(其他与 DFS 做法相同):
1 2 3 4 5 6 7
| for(int i = N; i >= 1; i--) { dp[i] = a[i]; for(int j = N; j > i; j--) if (connected[i][j]) dp[i] = max(dp[i], a[i] + dp[j]); ans = max(ans, dp[i]); }
|