算法 {树的换根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]
;