The rerooting technique

rerooting 是求解一类与树相关的问题的技巧。这类问题的特征:

  1. 定义了一个以有根树为自变量的函数 f
  2. 给你一个树 T
  3. 需要求出函数 f 在以 T 的每个点为根所得到的有根树上的值。

rerooting 在李煜东的《算法竞赛进阶指南》上名为“二次扫描与换根法”。

记号

Tr,u:树 T 以点 r 为根的有根树里以点 u 为根的子树。

Tu:=Tu,u

g 是一个关于有根树的函数。

g(r,u):=g(Tr,u)

g(r):=g(r,r)

Rerooting 的代码实现

过程

第一次扫描:通过典型的树上DP算出 f(1)

第二次扫描:深度优先遍历 T1,算出余下 n1 个点的 f 值。

代码框架

#include <vector>
const int max_n = 100000;              // number of vertices of the tree
std::vector<std::vector<int>> g(max_n);// store the tree, vertices are numbered from 0

void build_the_subtree_rooted_at(int u, int p) {
  for (int v : g[u]) {
    if (v != p) {
      build_the_subtree_rooted_at(v, u);
      // ...
    }
  }
}

// reroot the whole tree at u whose parent was p.
void reroot(int u, int p) {
    // p lost a son u
    // u got a son p
    // modify u but DO NOT modify p
    // It suffices to calculate what p becomes if it lost the son u,
    // you don't have to actually modify p.
}

void dfs(int u, int p) {
    // compute f(u)
    // other things...
  for (int v : g[u]) {
    if (v != p) {
      reroot(v, u);
      dfs(v, u);
    }
  }
}

int main() {
  build_the_subtree_rooted_at(0, -1);
  dfs(0, -1);
}

解释

计算 f(u) 所需要的信息一般有两类,

  1. 依赖于 Tu 的全局信息,这类信息一般可以在第一次扫描的过程中计算出来。
  2. 依赖于 Tu 的信息,把它记作 g(u):=g(Tu)g 是一个关于有根树的函数。

reroot(u, p) 的作用就是计算 g(u)。计算 g(u) 所需要的信息可分成五类,

  1. g(1,u),在 g(u) 算出来之后即被 g(u) 取代。
  2. 关于 T1,u 的除了 g(1,u) 之外的其他信息。
  3. 关于 T 的全局信息。
  4. g(p)
  5. 关于 Tu,p 的信息。

第 1, 2, 3 类信息都是在第一次扫描过程中算出来的,在第二次扫描过程中保持不变。在调用 reroot(u, p) 之前 g(p) 已经算出来了,也保持不变。第 5 类信息是在函数 reroot 里由前 4 类信息计算出来的,只在计算 g(u) 时用到,所以不必保存。总而言之,在 reroot(u, p) 所作的唯一更改就是用 g(u) 取代 g(1,u)。当然也可以不改变 g(1,u) 而把算出的 g(u) 另存他处。

Examples

CF1467E. Distinctive Roots in a Tree

提交:116070391

CF1092F. Tree with Maximum Cost

提交:116069700

POJ3585. Accumulation Degree

提交:31002137

posted @   Pat  阅读(251)  评论(0编辑  收藏  举报
编辑推荐:
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
阅读排行:
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· C# 从零开始使用Layui.Wpf库开发WPF客户端
· C#/.NET/.NET Core技术前沿周刊 | 第 31 期(2025年3.17-3.23)
· 接口重试的7种常用方案!
点击右上角即可分享
微信分享提示