【UOJ139】【UER #4】被刪除的黑白樹
題意:
很久很久以前,有一棵樹加入了 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 }
【UOJ139】【UER #4】被刪除的黑白樹