bzoj1304 [CQOI2009]叶子的染色
1304: [CQOI2009]叶子的染色
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 739 Solved: 490
[Submit][Status][Discuss]
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
0
1
0
1 4
2 5
4 5
3 5
Sample Output
2
HINT
M<=10000
分析:显然的树形dp题.状态也很显然,设f[i][0/1]表示以i为根的子树中,i的颜色为0/1的答案.通过子节点转移.f[i][0/1] = min{f[j][0/1] - 1,f[j][1/0]}.前面的-1是因为如果根和子节点颜色相同,则可以合并,使子节点颜色为透明.很容易想到颜色越在靠近根节点的点上确定,答案就越优.
一个问题:根是不确定的,如果枚举根,时间复杂度就为O(n^2).有点虚.事实上这样也能够通过这道题,但是还有O(n)的做法.我们不需要枚举根,任意非叶子节点作为根的答案是一样的.Why?假设a为根,b是a的子节点.要证明b作为根和a作为根的答案是一样的.如果a,b的颜色是一样的,这种情况是不存在的,因为可以使其中一个点的颜色为透明.如果一个点的颜色为透明,交换后将有颜色挪到根上,答案不变.如果两个点颜色不同,那么很显然,交换后答案也不会变.(满足了所有要求,并且没有加颜色).于是这道题就可以O(n)解决了.
树形dp遇到无根树要想办法转换成有根树求解,如果能证明无根树“等价于”有根树就更好了,否则可能就要枚举根.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 20010,inf = 0x7ffffff; int n,m,head[maxn],to[maxn],nextt[maxn],tot = 1,col[maxn],f[maxn][2]; void add(int x,int y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void dfs(int u,int fa) { if (u <= n) { f[u][col[u]] = 1; f[u][col[u] ^ 1] = inf; } else f[u][0] = f[u][1] = 1; for (int i = head[u];i;i = nextt[i]) { int v = to[i]; if (v == fa) continue; dfs(v,u); int temp1 = f[v][0],temp2 = f[v][1]; f[u][0] += min(temp1 - 1,temp2); f[u][1] += min(temp1,temp2 - 1); } } int main() { scanf("%d%d",&m,&n); for (int i = 1; i <= n; i++) scanf("%d",&col[i]); for (int i = 1; i < m; i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(n + 1,0); printf("%d\n",min(f[n + 1][0],f[n + 1][1])); return 0; }