【NOI复习】树链剖分
简介
树链剖分通常用来解决一类维护静态树上路径信息的问题, 例如:
给定一棵点带权树, 接下来每次操作会修改某条路径上所有点的权值(修改为同一个值或是同加上一个值等) , 以及询问某条路径上所有点的权值和。
当这棵树是一条链时, 这个问题实际上就是一个序列上区间修改、 区间询问的问题, 可以用之前介绍的几个数据结构解决。
对于其他情况, 由于树的形态是不变的, 因此树链剖分的策略是将这些点按某种方式组织起来, 剖分成为若干条链, 每条链就相当于一个序列, 则操作路径可以拆分为剖分好的某几条链, 也就是若干个完整序列或是某个序列上的一段区间, 此时再利用线段树等处理序列上区间操作问题的数据结构来解决。
树链剖分的核心就是如何恰当的剖分树为若干条链。 当链的划分方式确定后, 我们只要将它们看做是一个个序列, 将所有序列按顺序拼接起来后, 每条链就成为了一段区间, 而序列上的区间问题是我们所熟悉和擅长解决的。
方法
轻重链剖分
我们将树中的边分成两种: 轻边, 重边。 如下图中加粗的边是重边, 其余是轻边。
我们可以以任意点为根, 然后记 size(u) 为以 u 为根的子树的结点个数, 令 v 为u 所有儿子中 size 值最大的一个儿子, 则(u,v) 为重边, v 称为u 的重儿子。 u 到其余儿子的边为
轻边。
树链剖分求LCA
例题
【浙江省选2008】树的统计
题目背景
ZJOI2008 DAY1 T4
题目描述
一棵树上有 n 个节点,编号分别为 1 到 n ,每个节点都有一个权值 w 。
我们将以下面的形式来要求你对这棵树完成一些操作:
I.CHANGE u t :把结点 u 的权值改为 t ;
II.QMAX u v :询问从点 u 到点 v 的路径上的节点的最大权值;
III.QSUM u v :询问从点 u 到点 v 的路径上的节点的权值和。
注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。
输入格式
输入第一行为一个整数 n ,表示节点的个数。
接下来 n–1 行,每行 2 个整数 a 和 b ,表示节点 a 和节点 b 之间有一条边相连。
接下来 n 行,每行一个整数,第 i 行的整数 wi 表示节点 i 的权值。
接下来 1 行,为一个整数 q ,表示操作的总数。
接下来 q 行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
输出格式
对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。
样例数据 1
输入
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
输出
4
1
2
2
10
6
5
6
5
16
备注
【数据范围】
对于 100% 的数据,保证1<=n<=30000;0<=q<=200000;中途操作中保证每个节点的权值 w 在 -30000 到 30000 之间。
【题目分析】
模板题
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std; const int N = ;
const int oo = 0x3f3f3f3f; int dep[N], sze[N], top[N], son[N], pos[N], idx[N], val[N], fa[N];
int ecnt, adj[N], go[N << ], nxt[N << ], tot;
int sum[N * ], maxx[N * ];
int n, q; inline int Re(){
int i = , f = ; char ch = getchar();
for(; (ch < '' || ch > '') && ch != '-'; ch = getchar());
if(ch == '-') f = -, ch = getchar();
for(; ch >= '' && ch <= ''; ch = getchar())
i = (i << ) + (i << ) + (ch - '');
return i * f;
} inline void Wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) Wr(x / );
putchar(x % + '');
} inline void addEdge(const int &u, const int &v){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u;
} inline void dfs1(const int &u, const int &f){
dep[u] = dep[f] + ;
fa[u] = f;
sze[u] = ;
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs1(v, u);
sze[u] += sze[v];
if(sze[v] > sze[son[u]]) son[u] = v;
}
} inline void dfs2(const int &u, const int &f){
if(son[u]){ //先查重儿子, 保证重链连续
top[son[u]] = top[u];
idx[pos[son[u]] = ++tot] = son[u];
dfs2(son[u], u);
}
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f || v == son[u]) continue;
top[v] = v;
idx[pos[v] = ++tot] = v;
dfs2(v, u);
}
} inline int chkMax(const int &x, const int &y){
if(x > y) return x;
return y;
} inline void build(int k, int l, int r){
if(l == r){
sum[k] = maxx[k] = val[idx[l]];
return;
}
int mid = l + r >> , lc = k << , rc = k << | ;
build(lc, l, mid);
build(rc, mid + , r);
sum[k] = sum[lc] + sum[rc];
maxx[k] = chkMax(maxx[lc], maxx[rc]);
} inline int PathSum(int k, int l, int r, int x, int y){
if(x <= l && r <= y) return sum[k];
int mid = l + r >> , lc = k << , rc = k << | ;
int ret = ;
if(x <= mid) ret += PathSum(lc, l, mid, x, y);
if(y > mid) ret += PathSum(rc, mid + , r, x, y);
return ret;
} inline int PathMax(int k, int l, int r, int x, int y){
if(x <= l && r <= y) return maxx[k];
int mid = l + r >> , lc = k << , rc = k << | ;
int ret = -oo;
if(x <= mid) ret = chkMax(ret, PathMax(lc, l, mid, x, y));
if(y > mid) ret = chkMax(ret, PathMax(rc, mid + , r, x, y));
return ret;
} inline void PrintSum(int u, int v){
int ret = ;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
ret += PathSum(, , n, pos[top[u]], pos[u]);
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
ret += PathSum(, , n, pos[u], pos[v]);
Wr(ret), putchar('\n');
} inline void PrintMax(int u, int v){
int ret = -oo;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
ret = chkMax(ret, PathMax(, , n, pos[top[u]], pos[u]));
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
ret = chkMax(ret, PathMax(, , n, pos[u], pos[v]));
Wr(ret), putchar('\n');
} inline void modify(int k, int l, int r, int pos, int v){
if(l == r){
sum[k] = v;
maxx[k] = v;
return;
}
int mid = l + r >> , lc = k << , rc = k << | ;
if(pos <= mid) modify(lc, l, mid, pos, v);
else modify(rc, mid + , r, pos, v);
sum[k] = sum[lc] + sum[rc];
maxx[k] = chkMax(maxx[lc], maxx[rc]);
} inline void print(int k){
if(k == ) return;
print(k<<);print(k<<|);
cout<<sum[k]<<" "<<maxx[k]<<endl;
} int main(){
// freopen("h.in", "r", stdin);
n = Re();
for(int i = ; i < n; i++){
int a = Re(), b = Re();
addEdge(a, b);
}
for(int i = ; i <= n; i++) val[i] = Re(); dep[] = -, top[] = , idx[] = , pos[] = , tot = ;
dfs1(, );
dfs2(, );
build(, , n);
// print(1); q = Re();
for(int i = ; i <= q; i++){
char opt[]; int u, v, t;
scanf("%s", opt + );
if(opt[] == 'H'){ //change
u = Re(), t = Re();
modify(, , n, pos[u], t);
}
else if(opt[] == 'M'){ //qmax
u = Re(), v = Re();
PrintMax(u, v);
}
else{ //qsum
u = Re(), v = Re();
PrintSum(u, v);
}
}
}
【bzoj2243】【山东省选2011】染色
Description
给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这m个操作。
Input
第一行包含2个整数n和m,分别表示节点数和操作数;
第二行包含n个正整数表示n个节点的初始颜色
下面 行每行包含两个整数x和y,表示x和y之间有一条无向边。
下面 行每行描述一个操作:
“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;
“Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。
Output
对于每个询问操作,输出一行答案。
Sample Input
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5
Sample Output
1
2
HINT
数N<=10^5,操作数M<=10^5,所有的颜色C为整数且在[0, 10^9]之间。
【题目分析】
树链剖分,维护节点的颜色段数, 修改标记, 左端、右端颜色, 注意用左右子树更新根节点时颜色相同要-1, 数组线段树不好维护可以写成结构体!!
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std; const int N = 3e5; int n, m;
int sze[N], dep[N], val[N], idx[N], pos[N], fa[N], top[N], son[N], tot;
int ecnt, adj[N], go[N << ], nxt[N << ]; inline int Re(){
int i = , f = ; char ch = getchar();
for(; (ch < '' || ch > '') && ch != '-'; ch = getchar());
if(ch == '-') f = -, ch = getchar();
for(; ch >= '' && ch <= ''; ch = getchar())
i = (i << ) + (i << ) + (ch - '');
return i * f;
} inline void Wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) Wr(x / );
putchar(x % + '');
} inline void addEdge(const int &u, const int &v){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u;
} inline void dfs1(const int &u, const int &f){
dep[u] = dep[f] + ;
sze[u] = ;
fa[u] = f;
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs1(v, u);
sze[u] += sze[v];
if(sze[v] > sze[son[u]]) son[u] = v;
}
} inline void dfs2(const int &u, const int &f){
if(son[u]){
top[son[u]] = top[u];
idx[pos[son[u]] = ++tot] = son[u];
dfs2(son[u], u);
}
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f || v == son[u]) continue;
top[v] = v;
idx[pos[v] = ++tot] = v;
dfs2(v, u);
}
} struct node{
int cnt, lcol, rcol, tag;
node():cnt(), lcol(-), rcol(-), tag(-){}
}; namespace SegTree{
node tr[N * ];
inline void upt(int k){
int lc = k << , rc = k << | ;
tr[k].lcol = tr[lc].lcol;
tr[k].rcol = tr[rc].rcol;
tr[k].cnt = tr[lc].cnt + tr[rc].cnt - (tr[lc].rcol == tr[rc].lcol);
}
inline void cover(int k, int v){
tr[k].lcol = tr[k].rcol = v;
tr[k].cnt = ;
tr[k].tag = v;
}
inline void pushDown(int k){
int lc = k << , rc = k << | ;
if(tr[k].tag != -){
cover(lc, tr[k].tag);
cover(rc, tr[k].tag);
tr[k].cnt = , tr[k].lcol = tr[k].rcol = tr[k].tag;
tr[k].tag = -;
}
} inline void build(int k, int l, int r){
if(l == r){
tr[k].cnt = ;
tr[k].tag = -;
tr[k].lcol = tr[k].rcol = val[idx[l]];
return;
}
int mid = l + r >> , lc = k << , rc = k << | ;
build(lc, l, mid);
build(rc, mid + , r);
upt(k);
} inline void modify(int k, int l, int r, int x, int y, int v){
if(x <= l && r <= y){
cover(k, v);
return;
}
pushDown(k);
int mid = l + r >> , lc = k << , rc = k << | ;
if(x <= mid) modify(lc, l, mid, x, y, v);
if(y > mid) modify(rc, mid + , r, x, y, v);
upt(k);
} inline node query(int k, int l, int r, int x, int y){
if(l == x && r == y) return tr[k];
pushDown(k);
int mid = l + r >> , lc = k << , rc = k << | ;
if(y <= mid) return query(lc, l, mid, x, y);
else if(x > mid) return query(rc, mid + , r, x, y);
else {
node ret, ret1, ret2;
ret1 = query(lc, l, mid, x, mid);
ret2 = query(rc, mid + , r, mid + , y);
ret.cnt = ret1.cnt + ret2.cnt - (ret1.rcol == ret2.lcol);
ret.lcol = ret1.lcol, ret.rcol = ret2.rcol;
return ret;
}
// cout<<ret1.lcol<<" "<<ret1.lcol<<" "<<ret2.lcol<<" "<<ret2.rcol<<endl; }
}using namespace SegTree;
inline void PrintCnt(int a, int b){
int ans = , acol = -, bcol = -;
while(top[a] != top[b]){
if(dep[top[a]] < dep[top[b]]) swap(a, b), swap(acol, bcol);
node ret = query(, , n, pos[top[a]], pos[a]);
ans += ret.cnt;
if(ret.rcol == acol) ans--;
a = fa[top[a]], acol = ret.lcol;
}
if(dep[a] > dep[b]) swap(a, b), swap(acol, bcol);
node ret = query(, , n, pos[a], pos[b]);
ans += ret.cnt - (ret.lcol == acol) - (ret.rcol == bcol);
Wr(ans);
} inline void PathModify(int a, int b, int v){
while(top[a] != top[b]){
if(dep[top[a]] < dep[top[b]]) swap(a, b);
modify(, , n, pos[top[a]], pos[a], v);
a = fa[top[a]];
}
if(dep[a] > dep[b]) swap(a, b);
modify(, , n, pos[a], pos[b], v);
} int main(){
freopen("h.in", "r", stdin);
n = Re(), m = Re();
for(int i = ; i <= n; i++) val[i] = Re();
for(int i = ; i < n; i++){
int a = Re(), b = Re();
addEdge(a, b);
}
dep[] = -, top[] = pos[] = idx[] = tot = ;
dfs1(, );
dfs2(, );
build(, , n);
for(int i = ; i <= m; i++){
char opt; opt = getchar();
while(opt != 'Q' && opt != 'C') opt = getchar();
int a, b, c;
if(opt == 'C'){
a = Re(), b = Re(), c = Re();
PathModify(a, b, c);
}
else if(opt == 'Q'){
a = Re(), b = Re();
PrintCnt(a, b), putchar('\n');
}
}
return ;
}
【NOI复习】树链剖分的更多相关文章
- 算法复习——树链剖分模板(bzoj1036)
题目: 题目背景 ZJOI2008 DAY1 T4 题目描述 一棵树上有 n 个节点,编号分别为 1 到 n ,每个节点都有一个权值 w .我们将以下面的形式来要求你对这棵树完成一些操作:I.CHAN ...
- [BZOJ2243][SDOI2011]染色 解题报告|树链剖分
Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“ ...
- 7.18 NOI模拟赛 树论 线段树 树链剖分 树的直径的中心 SG函数 换根
LINK:树论 不愧是我认识的出题人 出的题就是牛掰 == 他好像不认识我 考试的时候 只会写42 还有两个subtask写挂了 拿了37 确实两个subtask合起来只有5分的好成绩 父亲能转移到自 ...
- 【NOI P模拟赛】校门外歪脖树上的鸽子(树链剖分)
题面 2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 , 1 ≤ l ≤ r ≤ n , 1 ≤ d ≤ 1 0 8 2 ≤ n ≤ 2 × 10^5,1 ≤ m ≤ 2 ...
- BZOJ 3672: [Noi2014]购票( 树链剖分 + 线段树 + 凸包 )
s弄成前缀和(到根), dp(i) = min(dp(j) + (s(i)-s(j))*p(i)+q(i)). 链的情况大家都会做...就是用栈维护个下凸包, 插入时暴力弹栈, 查询时就在凸包上二分/ ...
- 【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分+线段树区间合并
题解: 好像和noi那题并没有什么区别 只是加上了修改和变成树上 比较显然我们可以用树链剖分来维护
- 洛谷P4092 [HEOI2016/TJOI2016]树 并查集/树链剖分+线段树
正解:并查集/树链剖分+线段树 解题报告: 传送门 感觉并查集的那个方法挺妙的,,,刚好又要复习下树剖了,所以就写个题解好了QwQ 首先说下并查集的方法趴QwQ 首先离线,读入所有操作,然后dfs遍历 ...
- bzoj1036 [ZJOI2008]树的统计Count(树链剖分)
Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. Q ...
- BZOJ 3626: [LNOI2014]LCA 树链剖分 线段树 离线
http://www.lydsy.com/JudgeOnline/problem.php?id=3626 LNOI的树链剖分题没有HAOI那么水,学到的东西还是很多的. 我如果现场写,很难想出来这种题 ...
随机推荐
- MySQL存储汉字
之前在网上查找了很多方法,排在前排的都是修改配置文件my.ini的,没有成功,后来找到了一个解决方法: 在建表的时候,在语句后面加上段"engine = innodb default cha ...
- Hadoop集群搭建(非HA)
1.准备Linux环境 1.0先将虚拟机的网络模式选为NAT 1.1修改主机名 vi /etc/sysconfig/network NETWORKING=yes HOSTNAME=itcast ### ...
- 高CPU、数据库无法读写的真凶
有兴趣的同学可以参考如下系列文章,都是针对dump分析的实战和总结: Windbg DUMP分析(原创汇总) http://www.cnblogs.com/LoveOfPrince/p/6653341 ...
- Unexpected end of input 和 Unexpected token var 和 Unexpected token ;
在写jsp的时候使用的一段代码一直调试,出现Unexpected token ; 错误. 所以最后把代码各种精简,得到了如下的测试示例代码 <% String aaa="123&quo ...
- Python中的枚举
在Python中想要实现枚举功能的方式比较多,可以通过字典这一数据结构,利用键与值的对应关系,可以实现枚举的功能. my_Enum={ 'red':1, 'yellow':2, 'blue':3 } ...
- vue-schart : vue.js 的图表组件
介绍 vue-schart 是使用vue.js封装了sChart.js图表库的一个小组件.支持vue.js 1.x & 2.x 仓库地址:https://github.com/lin-xin/ ...
- GCTF2017部分write up
Misc stage1 拿到题看到是一张图片隐写 用神器Stegsolve看看发现左上角藏着一张二维码 截下来,不能直接扫,需要反色处理一下 扫出来是一串十六进制 看头几个十六进制数发现是pyc文件 ...
- 在64位Ubuntu系统上安装32位程序包
在64位Ubuntu系统上安装32位的程序包 $sudo apt-get install package_name:i386 例如: $sudo apt-get install openjdk-7-j ...
- android下的名词/片段解释
关于建项目时候SDK解释 minimum required SDK: 又为miniSdk, 是你程序最低支持的SDK版本,这个现在一般miniSDK设定一般为8或者10 Target SDK:是你程序 ...
- 纯CSS3美化单选按钮radio
这种纯CSS3美化单选按钮radio的方法适用于以下情况: 1.可兼容IE9以上,需要兼容IE8的要写IE的hack把样式去掉 2.只支持单选按钮radio,因为单选按钮选中样式的圆圈可以用CSS做出 ...