题目描述
给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少包含一个有色结点(哪怕是这个叶子本身)。 对于每个叶结点u,定义c[u]为从根结点从U的简单路径上最后一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。
输入
第一行包含两个正整数m, n,其中n是叶子的个数,m是结点总数。结点编号为1,2,…,m,其中编号1,2,… ,n是叶子。以下n行每行一个0或1的整数(0表示黑色,1表示白色),依次为c[1],c[2],…,c[n]。以下m-1行每行两个整数a,b(1<=a < b <= m),表示结点a和b 有边相连。
输出
仅一个数,即着色结点数的最小值。
样例输入
5 3
0
1
0
1 4
2 5
4 5
3 5
样例输出
2
题解
树形dp
考虑如果给定根节点的话怎么做:
设 $f[i][j]$ 表示以 $i$ 为根的子树,$i$ 到根节点的简单路径上最后一个有色节点的颜色是 $j$ 的最小着色点数。
那么对于所有 $i$ 的儿子 $k$ ,有 $f[i][j]+=min(f[k][j],f[k][j\text{^}1])$ 。边界条件 $f[u][c[u]]=0,f[u][c[u]\text{^}1]=\infty$ ,其中 $u$ 是叶子节点。
那么 $min(f[root][0],f[root][1])+1$ 就是 $root$ 作为树根时的答案,其中 $+1$ 指的是根节点需要再着色一次。
一次dp的时间复杂度是 $O(n)$ ,我们可以枚举每个节点为根,复杂度为 $O(n^2)$ ,可过。
但是还有更优的做法:考虑根节点从 $x$ 变化到相邻的点 $y$ 的过程,那么 $x$ 为根时,$y$ 的着色只有两种情况:染了与 $x$ 不同的颜色、没有染色。
第一种情况显然换根后方案可以不变,第二种情况显然可以换根时把 $x$ 的着色该为染 $y$ ,答案不变。因此有 $ans_y\le ans_x$,同时从 $y$ 换到 $x$ 时有 $ans_x\le ans_y$ ,所以 $ans_x=ans_y$。
于是选择任意一个非叶节点作为根做一次dp即可,时间复杂度 $O(n)$
#include <cstdio>
#include <algorithm>
#define N 10010
using namespace std;
int head[N] , to[N << 1] , next[N << 1] , cnt , f[N][2];
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
int i;
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa)
dfs(to[i] , x) , f[x][0] += min(f[to[i]][0] , f[to[i]][1] + 1) , f[x][1] += min(f[to[i]][1] , f[to[i]][0] + 1);
}
int main()
{
int n , m , i , x , y;
scanf("%d%d" , &m , &n);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &x) , f[i][x] = 0 , f[i][x ^ 1] = m;
for(i = 1 ; i < m ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x);
dfs(m , 0);
printf("%d\n" , min(f[m][0] , f[m][1]) + 1);
return 0;
}