好久好久之前,有一棵树加入了 UOJ 群。ios
这天,在它讨论“一棵树应该怎么旋转”的时候一不当心被删除了,变成了被删除的树。spa
忽然间,它忽然发现它失去了颜色,变成了一棵纯白的树。这让它感受很焦躁,因而它来拜托你给本身染上一些颜色。code
咱们能够把它描述成一棵n个节点的有根树(默认树的根为1号节点),全部非根的度数为1的节点被称为叶子节点。最开始全部的点都是白色的。blog
如今你须要选出一些节点并把这些节点染成黑色的。为了迎合树的审美,你的染色方案必需要知足全部叶子节点到根路径上的黑色节点个数相同。递归
你发现黑色节点个数越多,树就会越高兴,因此你想要知道在全部合法的染色方案中,黑色节点总个数最可能是多少。string
神题。(感受UOJ Round的题全是神题)it
$O(n^2)$的DP你们都会,可是跟正解并无什么关系;io
显然题目要求至关于使白色节点最少;class
有几个结论:stream
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 }