参考题目:Acwing 323. 战略游戏
题意
给定一个树,要使每条边上至少选一个点,求最少需要选几个点。
算法:树形dp
时间复杂度: O ( n ) O(n) O(n) (每个点只会被搜一次)
f
数组含义:
f[i][1]
:在i
号点放人的所有方案中的最小花费。f[i][0]
:在i
号点不放人的所有方案中的最小花费。- 根据定义,答案为
min(f[root][1], f[root][0])
,即根节点不放人和放人的所有方案中的最小值。
st
数组含义:
st[i] = true
,表示该点有父节点。st[i] = false
,表示该点没有父节点。
递归逻辑:
- DFS 先向下预处理出来子节点的方案,回溯时使用子节点的方案去更新父节点的方案。
转移方程:
u
:父节点编号,j
:子节点编号。f[u][0] += f[j][1]
:父节点不放人,那么子节点必须放人。- 因为,如果子节点不放人,
u -> j
这条边就不符合条件(这条边的两端点都没放人)。所以,在父节点不放人的情况下,子节点必须放人。 f[u][1] += min(f[j][1],f[j][0])
:父节点放人,取子节点放人或不放人两种中的最小值。- 如果父节点放人,其子节点放不放人皆可,所以取其最小的一个。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1510;
int n;
int f[N][2];
int h[N], e[N], ne[N], idx;
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
// 如果选该点,需要的人数是一。
f[u][1] = 1;
// 遍历所有的子节点。
for (int i = h[u]; ~i; i = ne[i])
{
// 取出来子节点编号。
int j = e[i];
// 预处理出子节点花费。
dfs(j);
// 转移方程。
f[u][0] += f[j][1];
f[u][1] += min(f[j][1], f[j][0]);
}
}
int main()
{
while (scanf("%d", &n) == 1)
{
// 多组输入,每次要重新初始化。
memset(h, -1, sizeof h);
memset(st, 0, sizeof st);
memset(f, 0, sizeof f);
idx = 0;
// 存图。
for (int i = 0; i < n; i ++ )
{
int id, m;
scanf("%d:(%d)", &id, &m);
while (m -- )
{
int ver;
scanf("%d", &ver);
add(id, ver);
st[ver] = true;
}
}
// 有向边,需要找到根节点。
int root = 0;
while (st[root]) root ++ ;
// 从根节点开始搜索。
dfs(root);
printf("%d\n", min(f[root][1], f[root][0]));
}
return 0;
}
参考资料
https://www.acwing.com/video/419/