Codeforces 219D Choosing Capital for Treeland(树形DP)

题目是给一张边有向的树形图。要选出首都的点,首都要都能走到其他点,因此要反转一些边的方向。问可以选哪几个点作为首都,使它们所需反转边的数量最少。

这题挺好想的,因为做过HDU2196。

  • 首先就不妨设正向边权值为0,反向边权值为1,那样就是各个点出发到其他点经过边所需的最少权值和。
  • 然后对于每个点,分两个部分考虑:以这个点为根的子树这个点往上走的部分
    1. dp[0][u]表示以u点作为首都且以u点为根的子树部分所需反转边的数量,容易知道就等于子树内边权和
    2. 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\n",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 }

 

你可能感兴趣的:(Codeforces 219D Choosing Capital for Treeland(树形DP))