【BalticOI2003】Gem 题解(树形DP)
题目大意:
给树上每一个结点赋值(值为正整数),要求相邻结点的权值不能相同。问树上最小权值和。$n\leq 10000$。
-------------------------
设$f[i][j]$表示以$i$为根的子树,根权值为$j$时子树的最小权值和。
朴素的$DP$是$n^3$的。这里我们有个结论:树上用到的颜色不超过$\log_{2} n$个。下面给出证明(orz大佬):
假设我们找到点$i$为树的重心。根据重心的性质,它的最大子树的结点数不超过$\frac{n}{2}$。考虑一种最差的情况:假设每棵子树的重心都要染上新的颜色。递归的次数是$log$次,所以树上用到的颜色不超过$\log_{2} n$个。
这样$j$最大是$14$,总复杂度$(\log^{2} n)*n$。
----------------------------------
更新:解释一下01染色法为什么不对。
假设现在有一棵树,某些结点它有很多很多个子节点(不是子树),为了保证答案的最优性,必须保证这些子节点权值为$1$。现在考虑父节点。假设这些父节点都是相连的,所以这些父节点权值不能相同。如果父节点的个数大于$2$,那么必须引入大于$2$的权值来保证最优解。因此01贪心法是不对的。
代码:
#include<bits/stdc++.h> using namespace std; int f[10005][21]; int head[20005],cnt; int n,ans=0x3f3f3f3f; struct node { int next,to; }edge[20005]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } void add(int from,int to) { edge[++cnt].next=head[from]; edge[cnt].to=to; head[from]=cnt; } void dfs(int now,int fa) { for (int i=1;i<=20;i++) f[now][i]=i; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; if (to==fa) continue; dfs(to,now); for (int j=1;j<=20;j++) { int minn=0x3f3f3f3f; for (int k=1;k<=20;k++) { if (j!=k) minn=min(minn,f[to][k]); } f[now][j]+=minn; } } } int main() { n=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); add(x,y);add(y,x); } dfs(1,0); for (int i=1;i<=20;i++) ans=min(ans,f[1][i]); printf("%d",ans); return 0; }