树链剖分 学习笔记

模板及讲解

树链剖分解决树上的修改问题。
将树剖成一条条链,再用线段树、树状数组等维护

相关代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define fo(i, j, k) for (i=(j);i<=(k);i++)
#define fd(i, k, j) for (i=(k);i>=(j);i--)
#define rd(a) scanf("%d", &a)
#define rd2(a, b) scanf("%d%d", &a, &b)
#define rd3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define ms(i, j) memset(i, j, sizeof i)
#define FN2 "bzoj1036"
using namespace std;
const int MAXN = 30000 + 5;
int dep[MAXN], son[MAXN], fa[MAXN], siz[MAXN]; //深度,重儿子,父亲,子树大小
int p[MAXN], top[MAXN], pre; //在线段树中的位置,所在重链顶部,线段树当前标号
int n, wi[MAXN];
vector<int> G[MAXN];
void dfs1(int u, int f)//第一次dfs记录值
{
int i;
dep[u] = dep[f] + 1, fa[u] = f, siz[u] = 1;
fo (i, 0, G[u].size()-1) {
int v = G[u][i];
if (v!=f) {
dfs1(v, u);
siz[u] += siz[v];
if (son[u]==-1||siz[son[u]]<siz[v]) son[u] = v;
}
}
}
void dfs2(int u, int chain) {//第二次dfs连重儿子成重链
int i;
p[u] = ++pre, top[u] = chain;
if (son[u]!=-1) {
dfs2(son[u], chain);
fo (i, 0, G[u].size()-1) {
int v = G[u][i];
if (v!=son[u]&&v!=fa[u]) dfs2(v, v);
}
}
}
int maxv[MAXN*4], sumv[MAXN*4];
void pushup(int o) {
int lc = o*2, rc = o*2+1;
maxv[o] = max(maxv[lc], maxv[rc]);
sumv[o] = sumv[lc] + sumv[rc];
}
void update(int o, int l, int r, int p, int v) {
int lc = o*2, rc = o*2+1, M = (l+r)/2;
if (l==r) {
sumv[o] = maxv[o] = v; return ;
}
if (p<=M) update(lc, l, M, p, v); else if (M<p) update(rc, M+1, r, p, v);
pushup(o);
}
int getMax(int o, int l, int r, int x, int y) {
int lc = o*2, rc = o*2+1, M = (l+r)/2, ret = -200000000;
if (x<=l&&r<=y) {
return maxv[o];
}
if (x<=M) ret = max(ret, getMax(lc, l, M, x, y));
if (M<y) ret = max(ret, getMax(rc, M+1, r, x, y));
return ret;
}
int getSum(int o, int l, int r, int x, int y) {
int lc = o*2, rc = o*2+1, M = (l+r)/2, ret = 0;
if (x<=l&&r<=y) {
return sumv[o];
}
if (x<=M) ret += getSum(lc, l, M, x, y);
if (M<y) ret += getSum(rc, M+1, r, x, y);
return ret;
}
int findMax(int u, int v)
{
int f1 = top[u], f2 = top[v];
int ret = -200000000;
while (f1!=f2) {
if (dep[f1]<dep[f2]) swap(f1, f2), swap(u, v);
ret = max(ret, getMax(1, 1, n, p[f1], p[u]));
u = fa[f1], f1 = top[u];
}
if (dep[u]<dep[v]) swap(u, v);
return max(ret, getMax(1, 1, n, p[v], p[u]));
}
int findSum(int u, int v)
{
int f1 = top[u], f2 = top[v];
int ret = 0;
while (f1!=f2) {
if (dep[f1]<dep[f2]) swap(f1, f2), swap(u, v);
ret += getSum(1, 1, n, p[f1], p[u]);
u = fa[f1], f1 = top[u];
}
if (dep[u]<dep[v]) swap(u, v);
return ret+getSum(1, 1, n, p[v], p[u]);
}
void init() {
int i; pre = 0;
fo (i, 1, n) dep[i] = fa[i] = siz[i] = p[i] = top[i] = 0, son[i] = -1, G[i].clear();
fo (i, 1, n*4) maxv[i] = -200000000, sumv[i] = 0;
fo (i, 1, n-1) {
int a, b; rd2(a, b);
G[a].push_back(b), G[b].push_back(a);
}
}
void solve() {
int q, i;
dfs1(1, 0);
dfs2(1, 1);
fo (i, 1, n) rd(wi[i]), update(1, 1, n, p[i], wi[i]);
rd(q);
fo (i, 1, q) {
char ch[10]; scanf("%s", ch);
if (ch[0]=='C') {
int u, t; rd2(u, t), update(1, 1, n, p[u], t);
} else if (ch[1]=='M') {
int u, v; rd2(u, v), printf("%d\n", findMax(u, v));
} else if (ch[1]=='S') {
int u, v; rd2(u, v), printf("%d\n", findSum(u, v));
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen(FN2".in","r",stdin);freopen(FN2".out","w",stdout);
#endif
while (rd(n)==1) init(), solve();
return 0;
}

常见题型

见 做题针对题目
1、点权问题
Q:修改某些点的权进行询问。
解:直接树剖进行线段树/树状数组维护
例题:bzoj1036
2、边权问题
Q:修改某些边的权进行询问。
解:树剖后维护点权,每个点的点权为这个点到他父亲之间边权,询问时删除lca的点权即可
例题:poj2763
3、子树问题
Q:修改结点u为根的子树的点权。
解:由树剖的性质可得,树剖后结点u为根的子树在线段树上的区间是连续的一段,那么记录一个左端点和右端点即可(时间戳思想)
例题:bzoj4034

------ 本文结束 ------