題目連結:http://codeforces.com/problemset/problem/219/D
題意:
給你一棵樹,n個節點。
樹上的邊都是有向邊,並且不一定是從父親指向兒子的。
你可以任意翻轉一些邊的方向。
現在讓你找一個節點,使得從這個節點出發能夠到達其他所有節點,並保證翻轉邊的數量最小。
問你最少翻轉多少條邊,並輸出所有滿足此條件的節點編號。
題解:
本題要解兩個dp: dp1 & dp2
首先考慮dp1:
表示狀態:
dp1[i]表示使節點i能夠到達i的子樹中的所有節點,翻轉邊的最小數量。
如何轉移:
dp1[i] = ∑ (dp1[son] + 邊<i,son>由i指向son ? 0 : 1)
dfs一遍即可。
然後求dp2:
表示狀態:
dp2[i]表示使節點i能夠到達這棵樹的所有節點,翻轉邊的最小數量。
如何轉移:
dp2[i] = dp2[par] + 邊<par,i>是否由par指向i ? 1 : -1
如果邊<par,i>由par指向i,那麼在dp2[par]中這條邊是不會被翻轉的,所以此時應該將它翻轉,代價+1。
如果邊<par,i>由i指向par,那麼在dp2[par]中這條邊已經被翻轉了一次,然而在dp2[i]中是不需要翻轉的,所以將dp2[par]中多餘的那次翻轉減掉就好。
邊界條件:
dp2[1] = dp1[1]
(預設根節點為1)
所以最終答案為dp2[i]中的最小值,然後將所有dp2[i]等於ans的節點輸出就好啦。
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #include <vector> 5 #define MAX_N 200005 6 #define INF 1000000000 7 8 using namespace std; 9 10 struct Edge 11 { 12 int dest; 13 bool flag; 14 Edge(int _dest,bool _flag) 15 { 16 dest=_dest; 17 flag=_flag; 18 } 19 Edge(){} 20 }; 21 22 int n; 23 int ans=INF; 24 int dp1[MAX_N]; 25 int dp2[MAX_N]; 26 vector<Edge> edge[MAX_N]; 27 28 void read() 29 { 30 cin>>n; 31 int x,y; 32 for(int i=1;i<n;i++) 33 { 34 cin>>x>>y; 35 edge[x].push_back(Edge(y,true)); 36 edge[y].push_back(Edge(x,false)); 37 } 38 } 39 40 void dfs1(int now,int p) 41 { 42 dp1[now]=0; 43 for(int i=0;i<edge[now].size();i++) 44 { 45 Edge temp=edge[now][i]; 46 if(temp.dest!=p) 47 { 48 dfs1(temp.dest,now); 49 dp1[now]+=dp1[temp.dest]+(!temp.flag); 50 } 51 } 52 } 53 54 void dfs2(int now,int p,bool d) 55 { 56 if(now==1) dp2[now]=dp1[now]; 57 else dp2[now]=dp2[p]+(d?1:-1); 58 ans=min(ans,dp2[now]); 59 for(int i=0;i<edge[now].size();i++) 60 { 61 Edge temp=edge[now][i]; 62 if(temp.dest!=p) dfs2(temp.dest,now,temp.flag); 63 } 64 } 65 66 void work() 67 { 68 dfs1(1,-1); 69 dfs2(1,-1,233); 70 cout<<ans<<endl; 71 for(int i=1;i<=n;i++) 72 { 73 if(dp2[i]==ans) cout<<i<<" "; 74 } 75 cout<<endl; 76 } 77 78 int main() 79 { 80 read(); 81 work(); 82 }