算法 {树的换根DP}

算法 {树的换根DP}

树的换根DP

定义

给定一个无根树T; 令T(x)表示 以x为树根的树T; 令DP(x)表示T(x)这个树的信息;
需求是: 求出所有的DP(x);

性质

不管什么算法, 我们并不会去枚举所有的T(x), 即枚举每个节点作为树根的情况, 这就成暴力了…
我们只会在T(0)这一棵树的基础上, 去研究 所有的T(x);

算法

直接递推

定义

只考虑T(0)这一棵树;
1: 先预处理出DP(0)的值;
2: 已知DP(fa)的值, 借此来推导出DP(son)的值;

代码
template< class _TypeTree_> void ___DP_ChangeTreeRoot( _TypeTree_ const& _tree){
    int N = _tree.PointsCount;
    static vector<int64_t> SubTreeSum; // `SubTreeSum[x]`表示 在*以0为树根*的这棵树中 `x`子树的权值和;
    static vector<int64_t> DP; // `DP[x]`表示以`x`为树根的`tree`这棵树 的权值;
    static vector<int8_t> __Vis;
    __Vis.resize(N);
    DP.resize( N);
    { // 构建`SubTreeSum`, 构建`DP[0]`
        DP[0] = 0;
        SubTreeSum.resize( N);
        std::memset( __Vis.data(), 0, sizeof(__Vis[0])*N);
        auto __Subtree = [&]( auto _dfs, int _cur, int _depth)->void{
            __Vis[_cur] = 1;
            SubTreeSum[_cur] = Val[_cur];
            DP[0] += (Val[_cur] * (int64_t)_depth);
            GRAPH_ITERATE_( _tree, _cur, nex, e){
                if( __Vis[nex] == 1){ continue;}
                _dfs( _dfs, nex, _depth+1);
                SubTreeSum[_cur] += SubTreeSum[nex];
            }
        };
        __Subtree( __Subtree, 0, 0);
    }

    //>> 换根DP (在已经得到`DP[0]`(根节点)的前提下, 即在得到`DP[fa]`的前提下 推导出`DP[son]`的值)
    std::memset( __Vis.data(), 0, sizeof(__Vis[0])*N);
    auto __DP = [&]( auto _dfs, int _cur)->void{
        __Vis[_cur] = 1;
        GRAPH_ITERATE_( _tree, _cur, nex, e){
            if( __Vis[nex] == 1){ continue;}
            { // 根据`DP[_cur]`(他一定正确) 推到出`DP[nex]`;
                DP[nex] = DP[_cur] - SubTreeSum[nex] + (SubTreeSum[0] - SubTreeSum[nex]);
            }
            _dfs( _dfs, nex);
        }
    };
    __DP( __DP, 0);
} // ___DP_ChangeTreeRoot

计算DP_up

定义

只考虑T(0)这一棵树; 令DP_up(x)T(0)中 除了x|x的所有子节点外的所有点 所构成的子树 的信息;
1: 先预处理出DP_up(0)的值 (他表示一个空树);
2: 已知DP_up(fa)的值, 借此来推导出DP_up(son)的值;

代码
template< class _TypeTree_> void ___DP_upTree( _TypeTree_ const& _tree){
//< &T0: *以0为树根*的这棵树`;  &UP(x): &T0这棵树中 除了`x|x的所有子节点`外的所有点 所构成的子树;
    int N = _tree.PointsCount;
    static vector<int64_t> SubTreeSum; // `SubTreeSum[x]`表示 &T0中 以`x`为根的子树的权值和;
    static vector<int64_t> DP_up; // `DP_up[x]`表示 &UP(x)的信息;
    static vector<int8_t> __Vis;
    __Vis.resize(N);
    { // 构建`SubTreeSum`
        SubTreeSum.resize( N);
        std::memset( __Vis.data(), 0, sizeof(__Vis[0])*N);
        auto __Subtree = [&]( auto _dfs, int _cur, int _depth)->void{
            __Vis[_cur] = 1;
            SubTreeSum[_cur] = Val[_cur];
            GRAPH_ITERATE_( _tree, _cur, nex, e){
                if( __Vis[nex] == 1){ continue;}
                _dfs( _dfs, nex, _depth+1);
                SubTreeSum[_cur] += SubTreeSum[nex];
            }
        };
        __Subtree( __Subtree, 0, 0);
    }
    //>> 换根DP (在已经得到`DP_up[0]`(根节点)的前提下, 即在得到`DP_up[fa]`的前提下 推导出`DP_up[son]`的值)
    DP_up.resize( N);
    DP_up[0] = 0; // `&UP(0)`是一个空树;
    std::memset( __Vis.data(), 0, sizeof(__Vis[0])*N);
    auto __DP_up = [&]( auto _dfs, int _cur)->void{
        __Vis[_cur] = 1;
        GRAPH_ITERATE_( _tree, _cur, nex, e){
            if( __Vis[nex] == 1){ continue;}
            { // 根据`DP_up[_cur]`(他一定正确) 推到出`DP_up[nex]`;
                DP_up[ nex] = 0; // 初始化
                DP_up[ nex] = DP_up[ _cur]; // `&UP(cur)`这些点
                DP_up[ nex] += [_cur]; // `cur`这一个点
                { // &UP(nex)中 除了`{&UP(cur),cur}`之外的所有点, 也就是: cur的 不属于`{nex,nex子节点}`的 *子节点*集合;
                    auto cur_son = Subtree(cur) - Subtree(nex) - (cur)单点; //  `Subtree(cur) - Subtree(nex) - (cur)单点`
                    DP_up[ nex] += cur_son;
                }
            }
            _dfs( _dfs, nex);
        }
    };
    __DP_up( __DP_up, 0);
} // ___DP_upTree

应用

@LINK: https://editor.csdn.net/md/?articleId=137600529;
直接递推;

@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=129839971;
DP_up;

笔记

     0
 1       2
3 4    5 6  7
           8 9

不管是dfs_down还是dfs_up, 他们的前提 都是这棵树是有根的!

首先引入一个概念, 任一点 都有两个概念: 向下树 和 向上树;
. 比如对于2节点, 他的向下树是2 5 6 7 8 9, 向上树是2 0 1 3 4;
. 也就是可以认为是, 以该节点做划分, 原来的树 就划分成了两个树;
. 向下树是: 树中任一节点 是当前节点必须经过其某一儿子才可以达到;
. 向上树是: 树中任一节点 是当前节点必须经过其父节点才可以达到;

--

dfs_down所维护的信息, 我们记作为DP_down 他表示的 就是向下树;
. 状态转移: DP_down[x] = DP_down[x的所有儿子]之和
. 比如DP_down[2]表示的 就是2 5 6 7 8 9向下树, DP_down[7]表示7 8 9向下树, 所以: DP_down[2] = DP_down[5] + DP_down[6] + DP_down[7];

dfs_up所维护的信息, 我们记作为DP_up 他表示的是向上树;
. 状态转移: DP_up[x] = DP_up[fa] + DP_down[fa的除x外的所有儿子]之和; (这一点与dfs_down不同);
. 比如DP_up[7]表示的是7 2 5 6 0 1 3 4向上树, 所以: DP_up[7] = DP_up[2] + DP_down[5, 6];

--

dfs_down( cur, fa): cur的答案还未知, 需要进行dfs_down( son, cur) 之后, 等回溯时 (即该dfs_down(son)函数执行完后) 来更新cur的答案, 即用DP_down[son]来更新DP_down[cur];

dfs_up( cur, fa): cur的答案已经知道了, 在执行dfs_up(son)之前 来更新son的答案, 即用DP_up[cur] + DP_down[cur的除son外的所有儿子]来更新DP_up[son];

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值