题解【CF219D】 换根法与树形dp

我最开始也一直不太理解换根法,后来自己慢慢磨懂了,写一篇题解,加深一下印象,也希望能帮助到大家

题意简述:给你一棵树,树上的边是有向的,选一个点(中心点),反转一些边,使这个点的能达到所有点,求这个点与最小反转次数。

做法:首先很容易想到的是先跑一次遍历,遍历时统计u的子树以u为中心点有多少边需要反转

(注:存图时存双向有权边,正向边权为0即反转0次,反向为1即反转1次)

void  dfs1(int u,int fa)
{ 
    for(int i=0;i<ed[u].size();++i)
    {
        int v=ed[u][i].to;
        if(v==fa) continue;
        dfs1(v,u);
        cnt[u]+=cnt[v]+ed[u][i].w;
    }
 }

接下来你会发现 cnt[u]只存储了以u为根节点的反转次数,而没有统计从父亲链接的带反转的边,但是有一个节点所统计的就是以这个节点作为中心点的值,

它就是根节点

(因为它没有父亲) 所以它儿子反转边之和就等于它的代价

那么就有一个点的cnt值等于所求值了

为了方便,再开一个数组f,其中f[root]=cnt[root]

如果我们知道节点u到节点v的边,节点u的代价,那么我们可以由此推出节点v的代价推理如下

对于u和v外侧的那些点,它们的目标方向没有改变,目标方向改变了的只有(u->v)变成了(v->u)

对这条边进行考虑 节点u到节点v 这条边本本身不需要反转,但我们把v节点作为中心点时,这条边就需要反转了

所以
$ for(u->v) f[u]=f[v]+1 $

$ for(v->u) f[u]=f[v]-1 $

所以我们再进行一次dfs

void dfs2(int u,int fa)
{
    for(int i=0;i<ed[u].size();++i)
    {
    int v=ed[u][i].to;
        if(v==fa) continue;
        f[v]=f[u]+(ed[u][i].w?-1:1);
        dfs2(v,u);
    }
} 

完整代码

总结一下:这个换根本质就是用$O(n)$的时间复杂度求出根节点的答案,再树形dp推出整棵树的答案,就相当于拿节点 u 当根节点的答案 推出 u的儿子 son 这样推出整棵树的大答案

时间复杂度$O(n)$

posted @ 2021-07-26 21:48  寂静的海底  阅读(2)  评论(0编辑  收藏  举报  来源