题目是给一张边有向的树形图。要选出首都的点,首都要都能走到其他点,因此要反转一些边的方向。问可以选哪几个点作为首都,使它们所需反转边的数量最少。
这题挺好想的,因为做过HDU2196。
- 首先就不妨设正向边权值为0,反向边权值为1,那样就是各个点出发到其他点经过边所需的最少权值和。
- 然后对于每个点,分两个部分考虑:以这个点为根的子树、这个点往上走的部分:
- dp[0][u]表示以u点作为首都且以u点为根的子树部分所需反转边的数量,容易知道就等于子树内边权和
- dp[1][u]表示以u点作为首都且u点向上部分所需反转边的数量,画下图就知道怎么转移了:dp[1][v] = ( dp[0][u]-dp[0][v]-weight(u,v) ) + dp[1][u] + weight(v,u) (v是u的孩子)
- 这样最后对于每个点u,它的答案就是这两部分之和了,即dp[0][u]+dp[1][u]。
感觉又学到一种树形DP的新姿势:分别考虑点往下的子树和点往上的父亲部分。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define INF (1<<30) 6 #define MAXN 222222 7 struct Edge{ 8 int u,v,w,next; 9 }edge[MAXN<<1]; 10 int NE,head[MAXN]; 11 void addEdge(int u,int v,int w){ 12 edge[NE].u=u; edge[NE].v=v; edge[NE].w=w; 13 edge[NE].next=head[u]; head[u]=NE++; 14 } 15 int d[2][MAXN]; 16 void dfs0(int u,int fa){ 17 for(int i=head[u]; i!=-1; i=edge[i].next){ 18 int v=edge[i].v; 19 if(v==fa) continue; 20 dfs0(v,u); 21 d[0][u]+=d[0][v]+edge[i].w; 22 } 23 } 24 void dfs1(int u,int fa){ 25 for(int i=head[u]; i!=-1; i=edge[i].next){ 26 int v=edge[i].v; 27 if(v==fa) continue; 28 d[1][v]=d[0][u]-d[0][v]-edge[i].w+d[1][u]+edge[i^1].w; 29 dfs1(v,u); 30 } 31 } 32 int main(){ 33 memset(head,-1,sizeof(head)); 34 int n,a,b; 35 scanf("%d",&n); 36 for(int i=1; i<n; ++i){ 37 scanf("%d%d",&a,&b); 38 addEdge(a,b,0); addEdge(b,a,1); 39 } 40 dfs0(1,1); 41 dfs1(1,1); 42 int res=INF; 43 for(int i=1; i<=n; ++i) res=min(res,d[0][i]+d[1][i]); 44 printf("%d ",res); 45 for(int i=1; i<=n; ++i){ 46 if(res==d[0][i]+d[1][i]){ 47 printf("%d ",i); 48 } 49 } 50 return 0; 51 }