题意:
很久很久以前,有一棵树加入了 UOJ 群。
这天,在它讨论“一棵树应该怎么旋转”的时候一不小心被删除了,变成了被删除的树。
突然间,它突然发现它失去了颜色,变成了一棵纯白的树。这让它感觉很焦躁,于是它来拜托你给自己染上一些颜色。
我们可以把它描述成一棵n个节点的有根树(默认树的根为1号节点),所有非根的度数为1的节点被称为叶子节点。最开始所有的点都是白色的。
现在你需要选出一些节点并把这些节点染成黑色的。为了迎合树的审美,你的染色方案必须要满足所有叶子节点到根路径上的黑色节点个数相同。
你发现黑色节点个数越多,树就会越高兴,所以你想要知道在所有合法的染色方案中,黑色节点总个数最多是多少。
题解:
神题。(感觉UOJ Round的题全是神题)
$O(n^2)$的DP大家都会,但是跟正解并没有什么关系;
显然题目要求相当于使白色节点最少;
有几个结论:
1.如果一种合法方案中根节点到所有叶节点的路径上都经过白色节点,那么dfs一遍这棵树,在遇到白色节点时染黑并回溯,这样必定可以使得白色节点变少或不变且依然合法;
2.如果一种合法方案中根节点到深度最浅的叶节点的路径上经过白色节点,那么根节点到所有叶节点的路径上必定都经过白色节点;假定根节点深度为1,因为其他叶节点的深度不小于到最浅叶节点的深度,而根节点到其他叶节点路径上的黑节点数等于到最浅叶节点路径上的黑节点数,而小于他们的深度,因此根节点到其他叶节点的路径上都会经过至少一个白点;
综上,一种最优的合法方案必定满足根节点到最浅叶节点的路径上没有白点;
这样一种构造方法就是先把根节点到最浅叶节点的路径全部染黑,然后递归判断根节点的其他子树跟是否要染白再往下做即可;
简化到每个点,实际上一个点被染白当且仅当该节点子树中最浅叶节点的深度>整棵树的最浅叶节点深度+该节点到根节点路径上的白点个数;
预处理每个点子树中的最浅叶节点深度,再一次dfs判断即可,时间复杂度$O(n)$。
代码:
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdio>
5 #include<cmath>
6 #include<queue>
7 #define inf 2147483647
8 #define eps 1e-9
9 using namespace std;
10 typedef long long ll;
11 typedef double db;
12 struct edge{
13 int v,next;
14 }a[200001];
15 int n,u,v,ans=0,tot=0,du[100001],mid[100001],head[100001];
16 void add(int u,int v){
17 a[++tot].v=v;
18 a[tot].next=head[u];
19 head[u]=tot;
20 }
21 void dfs(int u,int fa,int dpt){
22 if(u!=1&&du[u]==1)mid[u]=dpt;
23 for(int tmp=head[u];tmp!=-1;tmp=a[tmp].next){
24 int v=a[tmp].v;
25 if(v!=fa){
26 dfs(v,u,dpt+1);
27 mid[u]=min(mid[u],mid[v]);
28 }
29 }
30 }
31 void _dfs(int u,int fa,int nwd){
32 if(nwd<mid[u]){
33 nwd++;
34 ans++;
35 }
36 for(int tmp=head[u];tmp!=-1;tmp=a[tmp].next){
37 int v=a[tmp].v;
38 if(v!=fa){
39 _dfs(v,u,nwd);
40 }
41 }
42 }
43 int main(){
44 memset(head,-1,sizeof(head));
45 memset(mid,0x7f,sizeof(mid));
46 memset(du,0,sizeof(du));
47 scanf("%d",&n);
48 for(int i=1;i<n;i++){
49 scanf("%d%d",&u,&v);
50 add(u,v);
51 add(v,u);
52 du[u]++,du[v]++;
53 }
54 dfs(1,0,1);
55 _dfs(1,0,mid[1]);
56 printf("%d",n-ans);
57 return 0;
58 }