[算法学习] 换根dp
换根dp
一般来说,我们做题的树都是默认 \(1\) 为根的。但是有些题目需要计算以每个节点为根时的内容。
朴素的暴力:以每个点 \(u\) 作为 \(root\) 暴力dfs下去,复杂度\(O(n^2)\);
正确的做法:换根dp,复杂度\(O(n)\)。
执行步骤
- 第一次扫描,先默认 \(root=1\) ,跑一遍 \(dfs\);
- 第二次扫描,从 \(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的更多相关文章
- 换根 DP 学习笔记
前言 没脑子选手什么都不会. 正文 先来写一下换根 DP 的特点或应用方面: 不同的点作为树的根节点,答案不一样. 求解答案时要求出每一个节点的信息. 无法通过一次搜索完成答案的求解,因为一次搜索只能 ...
- 【换根DP】小奇的仓库
题目背景 小奇采的矿实在太多了,它准备在喵星系建个矿石仓库.令它无语的是,喵星系的货运飞船引擎还停留在上元时代! 题目内容 喵星系有\(n\)个星球,星球以及星球间的航线形成一棵树. 从星球\(a\) ...
- [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]
题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...
- 2018.10.15 NOIP训练 水流成河(换根dp)
传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...
- 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市
P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...
- 小奇的仓库:换根dp
一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...
- 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)
题意 题目链接:https://www.luogu.org/problem/P4827 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...
- Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)
题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...
- bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp
题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...
随机推荐
- 前端面试题整理——手写简易jquery
class jQuery { constructor(selector) { const result = document.querySelectorAll(selector) console.lo ...
- jsp+servlet上传excel并将数据导入到数据库表的实现方法
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- SQLite实现数据库的储存2+SQLite数据库可视化工具SQLite Stadio
今日所学 SQLite实现数据库的储存 查看数据库的两种方法 Android 中 SQLite 数据库的查看 - woider - 博客园 SQLite Studio安装教程 [SQLite]可视化工 ...
- 小程序中引入iconfont
注释:本人喜欢 font class, 你们可以依葫芦画瓢unicode和 symbol,,下面是 font class 演示: 1.选择好图标,然后: font class 2.步骤二: 复制 ...
- Spring Security 一键接入验证码登录和小程序登录
最近实现了一个多端登录的Spring Security组件,用起来非常丝滑,开箱即用,可插拔,而且灵活性非常强.我觉得能满足大部分场景的需要.目前完成了手机号验证码和微信小程序两种自定义登录,加上默认 ...
- FreeRTOS学习记录--任务创建函数详解
开局一张图.一步一步分析就好. (一)什么是任务? 在多任务系统中,我们按照功能不同,把整个系统分割成一个个独立的,且无法返回的函数,这个函数我们称为任务:任务包含几个属性:任务堆栈,任务函数.任务控 ...
- 如何科学衡量广告投放效果?HMS Core分析服务助您科学归因
日益多元化的广告形式以及投放成本的不断攀升,让广告主们更加关注每一次广告投放带来的实际价值. 然而,广告主一般仅能从平台获得展示.点击.下载等前端效果字段,实际的用户注册.激活等后端深度转化指标并无法 ...
- iOS全埋点解决方案-控件点击事件
前言 我们主要介绍如何实现控件点击事件($AppClick)的全埋点.在介绍如何实现之前,我们需要先了解一下,在 UIKit 框架下,处理点击或拖动事件的 Target-Action 设计模式. ...
- 2.Docker容器学习之新生入门必备基础知识
0x02 Docker 核心概念 描述:Docker的三大核心概念镜像/容器和仓库, 通过三大对象核心概念所构建的高效工作流程; 1.镜像 [image] 描述:images 类似于虚拟机镜像,借鉴了 ...
- Java学习day8
今天学习了package,import,final,static和多态 package可以理解为文件夹,因为有些类可能重名,如果在同一个目录下就无法正常实现,所有需要有不同的包来装对应的类 Java出 ...