换根dp

一般来说,我们做题的树都是默认 \(1\) 为根的。但是有些题目需要计算以每个节点为根时的内容。

朴素的暴力:以每个点 \(u\) 作为 \(root\) 暴力dfs下去,复杂度\(O(n^2)\);

正确的做法:换根dp,复杂度\(O(n)\)。

执行步骤

  1. 第一次扫描,先默认 \(root=1\) ,跑一遍 \(dfs\);
  2. 第二次扫描,从 \(root=1\) 开始,每次从 \(u\) 到 \(v\) 节点时,计算根从 \(u\) 转移到 \(v\) 时的贡献变化。

    很显然,换根dp是在两个\(dfs\)中完成的,下面我们介绍一下如何运用它。

例题1 Accumulation Degree

题目链接:South Central China 2008 Accumulation Degree

Description

给你一颗有 \(n\) 个节点的树,每一条边连接 \(u_i\) 和 \(v_i\),流量为 \(fl_i\) ,你需要找出一个点作为 \(root\),并最大化从该点出发到所有叶子节点的流量最大值。

多组数据。(PS:题意读不懂的可以结合题目中的图理解,类似网络流的流法)

数据范围 \(1 \le n\le 200000\),并且 \(\sum n \le 200000\)

时间限制 \(1000\ ms\)

Solution

我们先默认这棵树以 \(1\) 为根,跑一次 \(dfs\)。

定义 \(flow[i]\) 表示以 \(i\) 为根的子树中流量最大值

那么,\(u\) 节点从儿子 \(v\) 得到的流量为:

1.若\(v\)为叶子节点,那么\(flow[u] += flow[v]\)(可以直接流过来);

2.若\(v\)为非叶子节点,那么\(flow[u] += min(flow[v], fl(u, v))\)(\(u\)和\(v\)相连的边有流量限制)。

这样,我们得到了以 \(1\) 为根时的答案,记为 \(f[1]\),它的值等于 \(flow[1]\)。

考虑如何换根

从 \(u\) 为根转移到儿子 \(v\) 为根, \(f[v]\) 包括两部分:一部分是从 \(v\) 流向自己的子树,一部分是从 \(v\) 往父节点走。

那么贡献的变化是第二部分造成的,原本的贡献是 \(flow[u] - min(flow[v], fl(u, v))\),现在加上 \(u\) 到 \(v\) 这条边的流量限制,所以新的贡献是 \(min(fl(u, v), flow[u] - min(flow[v], fl(u, v)))\)。

注意如果 \(u\) 的度为 \(1\),则需要特殊处理。

再来一个 \(dfs\) 转移即可。

复杂度 \(O(n)\),可以通过本题。

Code

例题2 STA-Station

题目链接:POI2008 STA-Station

Description

给你一颗有 \(n\) 个节点的树,你需要找出一个点作为 \(root\) ,并最大化 \(\sum_{i=1}^{n} dep_i\)。

其中 \(dep_i\) 表示以 \(root\) 为根时,\(i\)节点的深度。

数据范围 \(1\le n\le 10^6\)

时间限制 \(1000\ ms\)

Solution

我们先默认这颗树以 \(1\) 为根,跑一次 \(dfs\),记录\(dep[i]\) 和\(size[i]\)。

接下来,定义 \(f_i\) 表示以 \(i\) 为根时的 \(dep[i]\) 之和。

显然,\(f[1] = \sum_{i=1}^{n} dep[i]\)。

当我们从 \(u\) 转移到儿子 \(v\) 时,以 \(v\) 为根的子树内的所有节点 \(dep\) 值都减一,以外的所有节点 \(dep\) 值都加一。

于是有: \(f[v] = f[u] - size[v] + (n - size[v]) = f[u] + n - 2 * size[v]\)。

答案即为 \(max_{i=1}^{n} f[i]\) 的 \(i\)。

复杂度 \(O(n)\),卡卡常可以通过本题。

Code

这个题目卡\(vector\),能把用\(STL\)的完美卡飞。所以我改成前向星了呜呜呜。

  1. // Author: wlzhouzhuan
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. #define ll long long
  5. #define ull unsigned long long
  6. #define rint register int
  7. #define rep(i, l, r) for (rint i = l; i <= r; i++)
  8. #define per(i, l, r) for (rint i = l; i >= r; i--)
  9. #define mset(s, _) memset(s, _, sizeof(s))
  10. #define pb push_back
  11. #define pii pair <int, int>
  12. #define mp(a, b) make_pair(a, b)
  13. #define Each(i) for (rint i = head[u]; i; i = edge[i].nxt)
  14. inline int read() {
  15. int x = 0, neg = 1; char op = getchar();
  16. while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  17. while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  18. return neg * x;
  19. }
  20. inline void print(int x) {
  21. if (x < 0) { putchar('-'); x = -x; }
  22. if (x >= 10) print(x / 10);
  23. putchar(x % 10 + '0');
  24. }
  25. const int N = 1000005;
  26. struct Edge {
  27. int to, nxt;
  28. } edge[N << 1];
  29. int head[N], tot;
  30. void add(int u, int v) {
  31. edge[++tot] = {v, head[u]};
  32. head[u] = tot;
  33. }
  34. int n;
  35. ll f[N];
  36. int sz[N], dep[N];
  37. void dfs1(int u, int fa) {
  38. sz[u] = 1;
  39. dep[u] = dep[fa] + 1;
  40. Each(i) {
  41. int v = edge[i].to;
  42. if (v == fa) continue;
  43. dfs1(v, u);
  44. sz[u] += sz[v];
  45. }
  46. }
  47. void dfs2(int u, int fa) {
  48. Each(i) {
  49. int v = edge[i].to;
  50. if (v == fa) continue;
  51. f[v] = f[u] + n - 2ll * sz[v];
  52. dfs2(v, u);
  53. }
  54. }
  55. int main() {
  56. ios :: sync_with_stdio(false); cin.tie(0);
  57. cin >> n;
  58. for (int i = 1; i < n; i++) {
  59. int u, v;
  60. cin >> u >> v;
  61. add(u, v), add(v, u);
  62. }
  63. dfs1(1, 0);
  64. for (int i = 1; i <= n; i++) f[1] += dep[i];
  65. dfs2(1, 0);
  66. cout << max_element(f + 1, f + n + 1) - f << '\n';
  67. return 0;
  68. }

[算法学习] 换根dp的更多相关文章

  1. 换根 DP 学习笔记

    前言 没脑子选手什么都不会. 正文 先来写一下换根 DP 的特点或应用方面: 不同的点作为树的根节点,答案不一样. 求解答案时要求出每一个节点的信息. 无法通过一次搜索完成答案的求解,因为一次搜索只能 ...

  2. 【换根DP】小奇的仓库

    题目背景 小奇采的矿实在太多了,它准备在喵星系建个矿石仓库.令它无语的是,喵星系的货运飞船引擎还停留在上元时代! 题目内容 喵星系有\(n\)个星球,星球以及星球间的航线形成一棵树. 从星球\(a\) ...

  3. [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]

    题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...

  4. 2018.10.15 NOIP训练 水流成河(换根dp)

    传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...

  5. 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市

    P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...

  6. 小奇的仓库:换根dp

    一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...

  7. 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)

    题意 ​ 题目链接:https://www.luogu.org/problem/P4827 ​ 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...

  8. Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)

    题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...

  9. bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp

    题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...

随机推荐

  1. JAVA对XML文件的读写

    XML 指可扩展标记语言(EXtensible Markup Language),是独立于软件和硬件的信息传输工具,应用于 web 开发的许多方面,常用于简化数据的存储和共享. xml指令处理指令,简 ...

  2. Exchange 2013 清空邮箱

    在某些应用场景中,需要清空用户邮箱的所有数据.如果使用Outlook web app或者Outlook 的邮件删除方式,对数以千计的邮件来说,实在不是一个好办法.exchange管理员可以使用&quo ...

  3. 手动封装一个node命令集工具

    了解NPM安装模块时与项目配置文件中的bin配置发生了什么 了解nodejs在控制台中的运行环境及上下文 基于自定义命令集工具集成Yeoman 一.NPM模块安装内幕与nodejs控制台运行环境 1. ...

  4. 技术管理进阶——什么Leader值得追随?

    原创不易,求分享.求一键三连 ​Leader眼里的主动性 前几天孙狗下面小A身上发生了一件Case,让他感到很疑惑: 有一个跨部门较多的项目推进不力,于是善于交流的他被临时提拔成项目负责人,但马上令人 ...

  5. springboot静态资源无法访问

    前言 今天使用springboot+layui+shiro实现一个前后端分离的商城后台系统,一个小小静态资源(image)问题搞了一下午:还好坚持了下来,否者崩溃.吐血都是小事 这是引入的路径 这是图 ...

  6. Unity-动画状态机使用细节记录

    Unity动画控制器Animator功能非常强大,总结一些具体使用细节,在动作游戏中很实用: 1.动画烘焙 不同动画之间,可能存在角色朝向,重心高度不一致: 可以在动画Eidt界面设置RootTran ...

  7. 从乘法求导法则到BPTT算法

    本文为手稿,旨在搞清楚为什么BPTT算法会多路反向求导,而不是一个感性的认识. 假设我们要对E3求导(上图中的L3),那么则有: 所以S2是W的函数,也就是说,我们不能说: 因为WS2 = WS2(w ...

  8. vue 组件复用 - component

    vue 组件复用 - component vue 组件复用 就是对 component 标签的使用 先看图 下图看使用 结果: 可以看到 在箱包 这一项,我将banner 组件用了两次,我 每次 点击 ...

  9. JavaScript基础第05天笔记

    JavaScript基础第05天笔记 1 - 作用域 1.1 作用域概述 通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域.作用域的使用提 ...

  10. 关于物理机没有VMnet1和VMnet8网卡的问题

    当我们在用虚拟机做实验需要与物理机进行连接时,发现无法连接上,这时候可能是没有Vmnet1或者Vmnet8网卡,又或者是Vmnet1和Vmnet8网卡都没有. 之前试过很多方法,重装.重启虚拟网络编辑 ...