CodeForces 219D Choosing Capital for Treeland (树形DP)经典

<题目链接>

题目大意:

给定一个有向树,现在要你从这颗树上选一个点,使得从这个点出发,到达树上其它所有点所需翻转的边数最小,输出最少需要翻转的边数,并且将这些符合条件的点输出。

解题分析:

比较经典的一种树形DP的模型。

$dp1[u]$表示以$u$为根的子树中最少需要翻转的边数(即$u$走到子树中所有的点需要翻转的边数),$dp2[u]$表示u向父亲方向走,需要翻转的边数。

$dp1$的转移方程很好写:$dp1[u]=dp1[u]+e[i].w$  (正向边$e[i].w=0$,反向边为1)

$dp2$的转移方程通过图像也能够比较直观的得到:$dp2[v]=dp1[u]-dp1[v]-e[i].w+e[i\^{1}].w+dp2[u];$

#include <bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T&x){
    x=0;int f=1;char c=getchar();
    while(c<'0'||c>'9'){ if(c=='-')f=-1;c=getchar(); }
    while(c>='0' && c<='9'){ x=x*10+c-'0'; c=getchar(); }
    x*=f;
}
const int N = 2e5+5;
#define REP(i,s,t) for(int i=s;i<=t;i++)
struct Edge{ int to,w,nxt; }e[N<<1];
int n,cnt;
int head[N],dp1[N],dp2[N];
inline void add(int u,int v,int w){
    e[cnt]=(Edge){v,w,head[u] };head[u]=cnt++;
}
void dfs1(int u,int pre){
    for(int i=head[u];~i;i=e[i].nxt){
        int v=e[i].to;
        if(v==pre)continue;
        dfs1(v,u);
        dp1[u]+=dp1[v]+e[i].w;
    }
}
void dfs2(int u,int pre){
    for(int i=head[u];~i;i=e[i].nxt){
        int v=e[i].to;
        if(v==pre)continue;
        dp2[v]=dp1[u]-dp1[v]+dp2[u]+(e[i].w?-1:1);      
        //dp2[v]=dp1[u]-dp1[v]-e[i].w+e[i^1].w+dp2[u];     
        //dp1[u]-dp1[u]-e[i].w+e[i^1].w表示v的父亲u的子树中,除v的子树的其它部分需要翻转的边数(从v向上走时),dp2[u]表示u向上的方向需要翻转的变数       
        dfs2(v,u);
    }
}
int main(){
    read(n);
    memset(head,-1,sizeof(head));
    REP(i,1,n-1){
        int u,v;read(u);read(v);
        add(u,v,0);add(v,u,1);
    }
    dfs1(1,-1);dfs2(1,-1);
    int ans=1e9;
    REP(i,1,n)ans=min(ans,dp1[i]+dp2[i]);
    cout<<ans<<endl;
    REP(i,1,n)if(ans==dp1[i]+dp2[i])cout<<i<<' ';
}   

猜你喜欢

转载自www.cnblogs.com/00isok/p/10905702.html
今日推荐