AcWing 323. 战略游戏(树形DP + 状态机DP)
- 一、问题
- 二、分析
- 1、思路分析
- 2、状态表示
- 3、状态转移
- 4、循环设计
- 5、初末状态
- 三、代码
一、问题
二、分析
1、思路分析
这道题最后问的其实就是,在一棵树中,每个边至少选择一个端点的条件下,我们最少能选择几个端点。
我们以一条边为例子, 两边的端点标号为A 和 B。那么如果A没选的话,B一定选。如果A选了的话,B可以选也可以不选。
如果我们把A和B看作两个物品的话,就会发现A选不选影响到了我们对B的选择。
那么我们就需要把A拿出来分类讨论,这个思路叫做状态机DP。
如果大家对状态机模型不太了解,或者想要深入全面地学习状态机模型的话,可以看作者在算法专栏中写的文章:第四十六章 动态规划——状态机模型
2、状态表示
f [ u ] [ j ] f[u][j] f[u][j]表示以 u u u为根节点的树中选,其中根节点 u u u的状态是 j j j的条件下,并且保证每条边至少选择一个点时,最少选择的点的数目。
3、状态转移
1表示选根节点,0表示不选根节点。
f
[
u
]
[
1
]
=
∑
m
i
n
(
f
[
s
o
n
]
[
1
]
,
f
[
s
o
n
]
[
0
]
)
f
[
u
]
[
0
]
=
∑
f
[
s
o
n
]
[
1
]
f[u][1] = \sum min(f[son][1],f[son][0]) \\ f[u][0]=\sum f[son][1]
f[u][1]=∑min(f[son][1],f[son][0])f[u][0]=∑f[son][1]
4、循环设计
我们需要遍历整个树,所以之前原本的for循环,由于数据结构的变化,我们需要用DFS代替。
5、初末状态
f
[
u
]
[
1
]
=
1
f[u][1] = 1
f[u][1]=1
因为我们选上了根节点,所以我们至少选一个。
最终的结果是: m i n ( f [ r o o t ] [ 0 ] , f [ r o o t ] [ 1 ] ) min(f[root][0], f[root][1]) min(f[root][0],f[root][1])
三、代码
由于我们是从根节点开始找,所以我们需要寻找根节点。因此,我们需要对子节点进行标记,最后没被标记的就是根节点。为了记录这些标记,我们可以开一个数组:bool not_root[N];
#include<bits/stdc++.h>
using namespace std;
const int N = 1510;
int n;
int h[N], e[N], ne[N], idx;
int f[N][2];
bool not_root[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, f[u][0] = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][1] = f[u][1] + min(f[j][1], f[j][0]);
f[u][0] = f[u][0] + f[j][1];
}
}
int main()
{
while (~scanf("%d", &n))
{
memset(h, -1, sizeof h); idx = 0;
memset(not_root, 0, sizeof not_root);
for (int i = 0; i < n; i ++ )
{
int a, b, siz;
scanf("%d:(%d) ", &a, &siz);
while (siz -- )
{
scanf("%d", &b);
add(a, b);
not_root[b] = true;
}
}
int root = 0;
while (not_root[root]) root ++ ;
dfs(root);
printf("%d\n", min(f[root][0], f[root][1]));
}
return 0;
}