又是一道优美的dp
Description
给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少包含一个有色结点(哪怕是这个叶子本身)。 对于每个叶结点u,定义c[u]为从根结点从U的简单路径上最后一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。
Input
第一行包含两个正整数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 有边相连。
Output
仅一个数,即着色结点数的最小值。
Sample Input
5 3
0
1
0
1 4
2 5
4 5
3 5
Sample Output
2
HINT
M<=10000
N<=5021
题目分析
这题的限制有些诡异,并且长得非常不像dp。仿佛我们需要三个状态$f[i][0/1/2]$来表示当前涂色状态,而且这样转移起来甚是麻烦。
但是细细一想,若一个点$node$有众多子节点,那就意味着有众多的叶子节点。而每一个叶子节点都是要上色的,况且上的色非黑即白就两种。
那么不论$node$子树内白多黑多,$node$这个节点当然要承担起父节点的责任,涂成一种颜色,这样肯定是不会更差的。
于是只有两个状态的树形dp就不难想到也不难打出了。
/**************************************************************
Problem: 1304
User: AntiQuality
Language: C++
Result: Accepted
Time:20 ms
Memory:1600 kb
****************************************************************/
#include<bits/stdc++.h>
const int maxn = ;
const int INF = 2e9;
int n,m,c[maxn];
int f[maxn][];
int edgeTot,edges[maxn<<],nxt[maxn<<],head[maxn];
int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void addedge(int u, int v)
{
edges[++edgeTot] = u, nxt[edgeTot] = head[v], head[v] = edgeTot;
edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
}
void dp(int now, int fa)
{
if (now <= n){
f[now][c[now]] = , f[now][-c[now]] = INF;
return;
}
f[now][] = , f[now][] = ;
for (int i=head[now]; i!=-; i=nxt[i])
{
int v = edges[i];
if (v!=fa){
dp(v, now);
f[now][] += std::min(f[v][], f[v][]-); //可能这里为什么f[v][1]不加1,而是f[v][0]减1会有点疑问
f[now][] += std::min(f[v][], f[v][]-); //f[now][]=1是每次dp时先确定好了的,这样可以避免重复计算
} //手推一下就好了
}
}
int main()
{
memset(head, -, sizeof head);
m = read(), n = read();
for (int i=; i<=n; i++) c[i] = read();
for (int i=; i<m; i++) addedge(read(), read());
dp(m, );
printf("%d\n",std::min(f[m][], f[m][]));
return ;
}
END