🐛 🐛 🐛
题意:给你一个树,边为有向边,选择某一点为根节点,要使该点能够到达所有点,问你最小花费为多少。
把两种树形DP(自下而上or自下而上)完美结合的好题 然而我不会
首先假设我们已经知道1点是根节点,要计算该点为根的最小翻转次数;
那么就另原图中的边权值为0,反向边权值为1.
另dp[ i ]为该点为根节点之后其子树 全部可达需要的翻转次数,这时候就应该从叶子结点开始转移:
- if 是叶子结点 dp[ u ] = 0;
- else dp[ u ] = Σ (dp[ v ] +edge[ u ][ v ]) //让子树全部满足条件之后连接该点与子树
这样计算完一个点为顶点的翻转次数,再令dp[ i ]为该点为根节点整棵树的翻转次数 ,这时候需要画图辅助。。
假设现在我们有一个非常友好的树是这个样子。。拿4这个结点举例,假设我们计算到这里的时候以2结点为根节点的dp已经计算过了,那么这时候再计算4的时候,假设我们这里。。咔擦。。把2,4之间的边剪掉,对于剩下的这部分,以2为根结点的翻转次数就是(dp[ 2 ] - dp[ 4 ] - 1)(这里的dp4还是以前的dp4,dp2已经不是以前的dp2了😳 ,再减1是因为2 4 连的边本来是要翻转的,现在边没了…) ;
那么以4作为根节点的翻转次数,其实就是第一次dfs得到的dp4(咔擦减下来的小树的翻转次数)+ dp[ 2 ] - dp[ 4 ] -1(剪下来的大树翻转次数)+ 2 4 之间的边是否需要翻转?1:0。
由计算过程可知,此次dfs 应该是自上而下的
化简即可得到最后的结果
struct IN
{
int v,w,nxt;
IN (){}
IN(int vv,int ww,int nn):v(vv),w(ww),nxt(nn){}
};
struct MP
{
IN edge[MAXN<<1];
int tot,head[MAXN];
void init(){tot=0;memset(head,0,sizeof(head));}
void add(int x,int y)
{
edge[++tot]=IN(y,0,head[x]);
head[x]=tot;
edge[++tot]=IN(x,1,head[y]);
head[y]=tot;
}
int Head(int i){return head[i];}
int next(int i){return edge[i].nxt;}
int vv(int i){return edge[i].v;}
int ww(int i){return edge[i].w;}
}mp;
int dp[MAXN];
void dfs1(int u,int fa)//自下而上DP
{
for(int i=mp.Head(u);i;i=mp.next(i))
{
int v=mp.vv(i),w=mp.ww(i);
if(v==fa) continue;
dfs1(v,u);
dp[u]+=dp[v]+w;
}
}
void dfs2(int u,int fa)//自上而下DP
{
for(int i=mp.Head(u);i;i=mp.next(i))
{
int v=mp.vv(i),w=mp.ww(i);
if(v==fa) continue;
dp[v]=dp[u]+(w?-1:1);
dfs2(v,u);
}
}
int main()
{
int n;cin>>n;mp.init();
rpp(i,n-1) {int x,y;cin>>x>>y;mp.add(x,y);}
dfs1(1,0); dfs2(1,0);
int ans=1<<30;
rpp(i,n) if(dp[i]<ans) ans=dp[i];
cout<<ans<<endl;
rpp(i,n) if(dp[i]==ans) cout<<i<<" ";
cout<<endl;
return 0;
}