换根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\)的完美卡飞。所以我改成前向星了呜呜呜。

// Author: wlzhouzhuan
#include <bits/stdc++.h>
using namespace std; #define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define Each(i) for (rint i = head[u]; i; i = edge[i].nxt)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
} const int N = 1000005;
struct Edge {
int to, nxt;
} edge[N << 1];
int head[N], tot;
void add(int u, int v) {
edge[++tot] = {v, head[u]};
head[u] = tot;
}
int n; ll f[N];
int sz[N], dep[N];
void dfs1(int u, int fa) {
sz[u] = 1;
dep[u] = dep[fa] + 1;
Each(i) {
int v = edge[i].to;
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
}
}
void dfs2(int u, int fa) {
Each(i) {
int v = edge[i].to;
if (v == fa) continue;
f[v] = f[u] + n - 2ll * sz[v];
dfs2(v, u);
}
}
int main() {
ios :: sync_with_stdio(false); cin.tie(0);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs1(1, 0);
for (int i = 1; i <= n; i++) f[1] += dep[i];
dfs2(1, 0);
cout << max_element(f + 1, f + n + 1) - f << '\n';
return 0;
}

[算法学习] 换根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. Android Studio 异常以及解决方案

    1. Error:(1, 0) Plugin is too old, please update to a more recent version, or set ANDROID_DAILY_OVER ...

  2. PAT B1031查验身份证

    一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8, ...

  3. 创建新的servlet一定要记得修改web..xml文件!!!

    创建新的servlet一定要记得修改web..xml文件!!!

  4. hibernate 联合主键 composite-id

    如果表使用联合主键(一个表有两个以上的主键),你可以映射类的多个属性为标识符属性.如:<composite-id>元素接受<key-property> 属性映射(单表映射)和& ...

  5. 基于node实现一个简单的脚手架工具(node控制台交互项目)

    实现控制台输入输出 实现文件读写操作 全原生实现一个简单的脚手架工具 实现vue-cli2源码 一.实现控制台输入输出 关于控制台的输入输出依然是基于node进程管理对象process,在proces ...

  6. webpack打包学习

    从上图我们可以看出,webpack 可以将多种静态资源 js.css.sass文件等转换成一个静态文件,以此可以减少页面的请求,从而提高浏览器响应速度 1.安装开发依赖包 npm install we ...

  7. 帝国cms修改成https后后台登陆空白的解决办法

    以下方法适用帝国cms7.5版本: 7.5版本已经有了http和https自动识别,但是因为一些疑难杂症的原因,自动识别判断的不准,后台登录也是空白, 我们可以打开e/config.php查找'htt ...

  8. zookeeper面试1-9

    1.选举机制 SID:服务器ID.用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致. ZXID:事务ID.ZXID是一个事务ID,用来标识一次服务器状态的变更.在某一时 ...

  9. 使用Kubeadm搭建高可用Kubernetes集群

    1.概述 Kubenetes集群的控制平面节点(即Master节点)由数据库服务(Etcd)+其他组件服务(Apiserver.Controller-manager.Scheduler...)组成. ...

  10. [译]ng指令中的compile与link函数解析 转

    通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre-link,post-link的用法与区别. 原文地址 angularjs里的指令非常神 ...