洛谷P2016《战略游戏》

最典型的树形DP

题目描述

Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。

他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。

注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。

请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵.

输入输出格式

输入格式

第一行 N,表示树中结点的数目。

第二行至第N+1行,每行描述每个结点信息,依次为:该结点标号i,k(后面有k条边与结点I相连)。

接下来k个数,分别是每条边的另一个结点标号r1,r2,…,rk。

对于一个n(0<n<=1500)个结点的树,结点标号在0到n-1之间,在输入数据中每条边只出现一次。

输出格式

输出文件仅包含一个数,为所求的最少的士兵数目。

例如,对于如下图所示的树:

1
2
3
       0
1
2 3

答案为1(只要一个士兵在结点1上)。

输入输出样例

输入样例

1
2
3
4
5
4
0 1 1
1 2 2 3
2 0
3 0

输出样例

1
1

解题思路

这道题,就是这种树形DP最标准的形态
「选点DP」


dp[i][0/1] 表示选/不选以i为根的子树时的最大值

转移方程很显然
dp[root][0] += dp[child][1]
dp[root][1] += std::min(dp[child][0], dp[child][1])

也就是

  • 如果我不选当前点,那么就必须选我儿子,不然我和我儿子之间这条路没人看

  • 如果我选了当前点,我儿子干啥我是不管的,选一个最小的加上

代码实现

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
48
49
#include <iostream>
#include <vector>

#define FILE_IN(__fname) freopen(__fname, "r", stdin)
#define FILE_OUT(__fname) freopen(__fname, "w", stdout)
#define IMPROVE_IO() std::ios::sync_with_stdio(false)

using std::cin;
using std::cout;
using std::endl;

const int MAXN = 1500 + 10;

int n, dp[MAXN][2];
std::vector<int> G[MAXN];

inline void addEdge(int prev, int next) {
G[prev].push_back(next);
G[next].push_back(prev);
}

void DFS(int root = 1, int father = 0) {
dp[root][1] = 1; dp[root][0] = 0;
for (auto v : G[root]) {
if (v == father) continue;
DFS(v, root);
dp[root][1] += std::min(dp[v][0], dp[v][1]);
dp[root][0] += dp[v][1];
}
}

int main() {
IMPROVE_IO();
cin >> n;
for (int i = 1; i <= n; ++i) {
int id = 0, k = 0;
cin >> id >> k;
++id;
for (int i = 1; i <= k; ++i) {
int qwq = 0;
cin >> qwq;
++qwq;
addEdge(id, qwq);
}
}
DFS();
cout << std::min(dp[1][0], dp[1][1]) << endl;
return 0;
}