D. Choosing Capital for Treeland(两种树形DP)

🐛 🐛 🐛
题意:给你一个树,边为有向边,选择某一点为根节点,要使该点能够到达所有点,问你最小花费为多少。
把两种树形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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值