<题目链接>
题目大意:
给定一个有向树,现在要你从这颗树上选一个点,使得从这个点出发,到达树上其它所有点所需翻转的边数最小,输出最少需要翻转的边数,并且将这些符合条件的点输出。
解题分析:
比较经典的一种树形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<<' '; }