树链剖分
定义
是指将一棵树划分成若干条链。常见的剖分方式有长链剖分、重链剖分、实链剖分。
重链剖分
从轻重儿子的角度将树划分成多条“重链”,进而将重链转换成区间问题解决。
步骤
- dfs 预处理一棵树的深度、父节点、子树大小、重儿子(\(dep, fa, sz, son\))。
- 对于每个轻儿子,再次 dfs,维护每个点的 dfs 序(\(dfn_x\))。递归时,优先去一个点的重儿子。重儿子继承父节点的链头信息(\(top_x\)),轻儿子自己就是链头。
- 对连续的 dfs 序维护数据结构,维护相应信息即可。
性质
- 重儿子不一定唯一,但确定重儿子后重链剖分唯一。
- 一条重链总是从轻儿子开始,其余点为重儿子。
- 一条重链的 dfs 序连续。
- 一棵子树的 dfs 序连续。
- 任意点 \(x\) 到其链头 \(top_x\) 的 dfs 序连续,且深度小的点 dfs 序小。
- 从任意点 \(x\) 出发到根节点,中间经过的不同的重链至多 \(\log n\) 条(切换重链子树大小起码翻倍)。
- 树上任意一条路径 \((x, y)\) 总是可以由不超过 \(\log n\) 段重链的子链拼接而成。
例题
P3384 【模板】重链剖分/树链剖分
模板。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int n, Q, u, v, w, op, mod, root, a[N], id[N];
vector<int> g[N];
namespace Segment_Tree {
#define mid ((L + R) >> 1)
#define son p, L, R
#define lson ls(p), L, mid
#define rson rs(p), mid + 1, R
int sum[N << 2], pls[N << 2];
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
inline void psup(int p) {
sum[p] = (sum[ls(p)] + sum[rs(p)]) % mod;
return ;
}
inline void build(int p = 1, int L = 1, int R = n) {
if(L == R) {
sum[p] = a[id[L]] % mod;
return ;
}
build(lson), build(rson), psup(p);
return ;
}
inline void work(int p, int L, int R, int k) {
pls[p] = (pls[p] + k) % mod;
sum[p] = (sum[p] + (R - L + 1) * k % mod) % mod;
return ;
}
inline void psd(int p, int L, int R) {
if(! pls[p]) return ;
work(lson, pls[p]), work(rson, pls[p]);
pls[p] = 0;
return ;
}
inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) {
work(son, k);
return ;
}
psd(son);
if(l <= mid) add(l, r, k, lson);
if(r > mid) add(l, r, k, rson);
psup(p);
return ;
}
inline int query(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return sum[p];
int res = 0;
psd(son);
if(l <= mid) res = (res + query(l, r, lson)) % mod;
if(r > mid) res = (res + query(l, r, rson)) % mod;
return res;
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace Segment_Tree;
namespace Graph {
int times, sz[N], fa[N], dep[N], son[N], dfn[N], top[N];
inline void dfs(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
fa[u] = x;
dep[u] = dep[x] + 1;
dfs(u, x);
sz[x] += sz[u];
if(sz[u] > sz[son[x]]) son[x] = u;
}
return ;
}
inline void DFS(int x) {
dfn[x] = ++ times;
id[dfn[x]] = x;
if(son[x]) {
top[son[x]] = top[x];
DFS(son[x]);
}
for(auto u : g[x])
if(! top[u]) {
top[u] = u;
DFS(u);
}
return ;
}
inline void Add(int x, int y, int k) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
add(dfn[top[x]], dfn[x], k);
x = fa[top[x]];
}
if(dfn[x] > dfn[y]) swap(x, y);
add(dfn[x], dfn[y], k);
return ;
}
inline int Query(int x, int y) {
int res = 0;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = (res + query(dfn[top[x]], dfn[x])) % mod;
x = fa[top[x]];
}
if(dfn[x] > dfn[y]) swap(x, y);
res = (res + query(dfn[x], dfn[y])) % mod;
return res % mod;
}
}
using namespace Graph;
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q >> root >> mod;
for(int i = 1 ; i <= n ; ++ i)
cin >> a[i];
for(int i = 1 ; i < n ; ++ i) {
cin >> u >> v;
g[u].pb(v), g[v].pb(u);
}
dfs(root, -1);
top[root] = root;
DFS(root);
build();
while(Q --) {
cin >> op >> u;
if(op == 1) {
cin >> v >> w;
Add(u, v, w);
}
else if(op == 2) {
cin >> v;
cout << Query(u, v) << '\n';
}
else if(op == 3) {
cin >> w;
add(dfn[u], dfn[u] + sz[u] - 1, w);
}
else cout << query(dfn[u], dfn[u] + sz[u] - 1) << '\n';
}
return 0;
}
P1505 [国家集训队] 旅游
路径操作,考虑将路径挂在深度较大的那个点。
需要注意的是,路径操作时当两个点在同一条重链上时,深度较浅的点不计入统计。
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 2e5 + 5;
int n, Q, x, y, z, a[N], u[N], v[N], w[N], id[N];
string op;
vector<int> g[N];
namespace Segment_Tree {
#define mid ((L + R) >> 1)
#define son p, L, R
#define lson ls(p), L, mid
#define rson rs(p), mid + 1, R
int mx[N << 2], mn[N << 2], sum[N << 2], mul[N << 2];
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
inline void psup(int p) {
mx[p] = max(mx[ls(p)], mx[rs(p)]);
mn[p] = min(mn[ls(p)], mn[rs(p)]);
sum[p] = sum[ls(p)] + sum[rs(p)];
return ;
}
inline void build(int p = 1, int L = 1, int R = n) {
mx[p] = -1e18;
mn[p] = 1e18;
mul[p] = 1;
if(L == R) {
mx[p] = mn[p] = sum[p] = a[id[L]];
return ;
}
build(lson), build(rson), psup(p);
return ;
}
inline void work(int p, int k) {
mx[p] *= k;
mn[p] *= k;
mul[p] *= k;
sum[p] *= k;
swap(mx[p], mn[p]);
return ;
}
inline void psd(int p, int L, int R) {
if(mul[p] == 1) return ;
work(ls(p), mul[p]), work(rs(p), mul[p]);
mul[p] = 1;
return ;
}
inline void modify(int x, int k, int p = 1, int L = 1, int R = n) {
if(L == R) {
mx[p] = mn[p] = sum[p] = k;
return ;
}
psd(son);
if(x <= mid) modify(x, k, lson);
else modify(x, k, rson);
psup(p);
return ;
}
inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) {
work(p, k);
return ;
}
psd(son);
if(l <= mid) add(l, r, k, lson);
if(r > mid) add(l, r, k, rson);
psup(p);
return ;
}
inline int querysum(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return sum[p];
int res = 0;
psd(son);
if(l <= mid) res += querysum(l, r, lson);
if(r > mid) res += querysum(l, r, rson);
return res;
}
inline int querymax(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return mx[p];
int res = -1e18;
psd(son);
if(l <= mid) res = max(res, querymax(l, r, lson));
if(r > mid) res = max(res, querymax(l, r, rson));
return res;
}
inline int querymin(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return mn[p];
int res = 1e18;
psd(son);
if(l <= mid) res = min(res, querymin(l, r, lson));
if(r > mid) res = min(res, querymin(l, r, rson));
return res;
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace Segment_Tree;
namespace Graph {
int times, sz[N], fa[N], dep[N], son[N], dfn[N], top[N];
inline void dfs(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
fa[u] = x;
dep[u] = dep[x] + 1;
dfs(u, x);
sz[x] += sz[u];
if(sz[u] > sz[son[x]]) son[x] = u;
}
return ;
}
inline void DFS(int x) {
dfn[x] = ++ times;
id[dfn[x]] = x;
if(son[x]) {
top[son[x]] = top[x];
DFS(son[x]);
}
for(auto u : g[x])
if(! top[u]) {
top[u] = u;
DFS(u);
}
return ;
}
inline void Add(int x, int y, int k) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
add(dfn[top[x]], dfn[x], k);
x = fa[top[x]];
}
if(dfn[x] > dfn[y]) swap(x, y);
add(dfn[x] + 1, dfn[y], k);
return ;
}
inline int Querysum(int x, int y) {
int res = 0;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res += querysum(dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
if(dfn[x] > dfn[y]) swap(x, y);
res += querysum(dfn[x] + 1, dfn[y]);
return res;
}
inline int Querymax(int x, int y) {
int res = -1e18;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = max(res, querymax(dfn[top[x]], dfn[x]));
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
res = max(res, querymax(dfn[x] + 1, dfn[y]));
return res;
}
inline int Querymin(int x, int y) {
int res = 1e18;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = min(res, querymin(dfn[top[x]], dfn[x]));
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
res = min(res, querymin(dfn[x] + 1, dfn[y]));
return res;
}
}
using namespace Graph;
signed main() {
// freopen("P1505_1.in", "r", stdin);
// freopen("asd.out", "w", stdout);
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n ;
for(int i = 1 ; i < n ; ++ i) {
cin >> u[i] >> v[i] >> w[i];
++ u[i], ++ v[i];
g[u[i]].pb(v[i]), g[v[i]].pb(u[i]);
}
dfs(1, -1);
top[1] = 1;
DFS(1);
for(int i = 1 ; i < n ; ++ i) {
if(dep[u[i]] < dep[v[i]]) swap(u[i], v[i]);
a[u[i]] = w[i];
}
build();
cin >> Q;
while(Q --) {
cin >> op;
if(op == "C") {
cin >> x >> z;
modify(dfn[u[x]], z);
}
else if(op == "N") {
cin >> x >> y;
++ x, ++ y;
Add(x, y, -1);
}
else if(op == "SUM") {
cin >> x >> y;
++ x, ++ y;
cout << Querysum(x, y) << '\n';
}
else if(op == "MAX") {
cin >> x >> y;
++ x, ++ y;
cout << Querymax(x, y) << '\n';
}
else {
cin >> x >> y;
++ x, ++ y;
cout << Querymin(x, y) << '\n';
}
}
return 0;
}
P2486 [SDOI2011] 染色
前情提要:简化版(序列)
线段树维护半群即可。
- 将区间问题搬到树上,则可以重剖区间。
- 树上统计时换重链时,两条链的交汇处是否颜色一致。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int n, Q, u, v, w, a[N];
char op;
namespace Graph {
int times, id[N], sz[N], fa[N], dep[N], son[N], top[N], dfn[N];
vector<int> g[N];
inline void dfs1(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
fa[u] = x;
dep[u] = dep[x] + 1;
dfs1(u, x);
sz[x] += sz[u];
if(sz[son[x]] < sz[u]) son[x] = u;
}
return ;
}
inline void dfs2(int x) {
dfn[x] = ++ times;
id[dfn[x]] = x;
if(son[x]) {
top[son[x]] = top[x];
dfs2(son[x]);
}
for(auto u : g[x])
if(! top[u]) {
top[u] = u;
dfs2(u);
}
return ;
}
}
using namespace Graph;
namespace Segment_Tree {
#define mid ((L + R) >> 1)
#define son p, L, R
#define lson ls(p), L, mid
#define rson rs(p), mid + 1, R
struct Node {
int lc, rc, res, cov;
} t[N << 2];
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
inline void psup(int p) {
t[p].lc = t[ls(p)].lc;
t[p].rc = t[rs(p)].rc;
t[p].res = t[ls(p)].res + t[rs(p)].res;
if(t[ls(p)].rc == t[rs(p)].lc) -- t[p].res;
return ;
}
inline void build(int p = 1, int L = 1, int R = n) {
if(L == R) {
t[p].res = 1;
t[p].lc = t[p].rc = a[id[L]];
return ;
}
build(lson), build(rson), psup(p);
return ;
}
inline void work(int p, int k) {
t[p].res = 1;
t[p].cov = k;
t[p].lc = t[p].rc = k;
return ;
}
inline void psd(int p, int L, int R) {
if(! t[p].cov) return ;
work(ls(p), t[p].cov), work(rs(p), t[p].cov);
t[p].cov = 0;
return ;
}
inline Node merge(Node x, Node y) {
Node z;
z.lc = x.lc;
z.rc = y.rc;
z.res = x.res + y.res;
if(x.rc == y.lc) -- z.res;
return z;
}
inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) {
work(p, k);
return ;
}
psd(son);
if(l <= mid) add(l, r, k, lson);
if(r > mid) add(l, r, k, rson);
psup(p);
return ;
}
inline int ask(int x, int p = 1, int L = 1, int R = n) {
if(L == R) return t[p].lc;
psd(son);
if(x <= mid) return ask(x, lson);
else return ask(x, rson);
}
inline Node query(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return t[p];
psd(son);
Node res = {-1, -1, 0, 0};
if(r <= mid) return query(l, r, lson);
if(l > mid) return query(l, r, rson);
return merge(query(l, r, lson),query(l, r, rson));
}
inline void Add(int x, int y, int z) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
add(dfn[top[x]], dfn[x], z);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
add(dfn[x], dfn[y], z);
return ;
}
inline int Query(int x, int y) {
int res = 0, cx = -1, cy = -1;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y), swap(cx, cy);
res += query(dfn[top[x]], dfn[x]).res;
int X = ask(dfn[x]), Y = ask(dfn[top[x]]);
if(X == cx) -- res;
cx = Y;
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y), swap(cx, cy);
res += query(dfn[x], dfn[y]).res;
int X = ask(dfn[x]), Y = ask(dfn[y]);
if(X == cx) -- res;
if(Y == cy) -- res;
return res;
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace Segment_Tree;
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q;
for(int i = 1 ; i <= n ; ++ i)
cin >> a[i];
for(int i = 1 ; i < n ; ++ i) {
cin >> u >> v;
g[u].pb(v), g[v].pb(u);
}
dfs1(1, -1);
top[1] = 1;
dfs2(1);
build();
while(Q --) {
cin >> op >> u >> v;
if(op == 'C') {
cin >> w;
Add(u, v, w);
}
else cout << Query(u, v) << '\n';
}
return 0;
}
P3313 [SDOI2014] 旅行(待补)
- 对于每一种宗教维护一棵线段树,\(rt_i\) 表示宗教 \(i\) 线段树的根节点。
- 一种解决方法是使用动态开点线段树,
build
时创建 \(O(n \log n)\) 个节点,\(Q\) 次修改共增加 \(O(Q \log n)\) 个节点,可行。 - 另一种思路是使用分块。维护 \(sum_{c, pos}\) 表示宗教 \(c\) 在第 \(pos\) 个块的权值和,\(mx_{c, pos}\) 表示宗教 \(c\) 在第 \(pos\) 个块的权值最大值。
P7735 [NOI2021] 轻重边
- 初始时给每个点随便赋 \(1-n\) 的不同权值,\(col\) 表示颜色。
- 对于操作 \(1\),每次将 \(col \to col + 1\),将 \(a \to b\) 的路径所有点染色为 \(col\)。只有 \(a \to b\) 路径上的边两个端点同色,路径上的点和路径外的邻接点颜色一定不同。
- 则重边为两端颜色相同的边,轻边为两端颜色不同的边。
- 操作 \(2\),转化为统计 \(a \to b\) 路径上连续相同的颜色段中重边的数量,线段树维护,类比 P2486 [SDOI2011] 染色。
代码:
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int T, n, Q, u, v, w, op, col, a[N];
namespace Graph {
int times, id[N], sz[N], fa[N], dep[N], son[N], top[N], dfn[N];
vector<int> g[N];
inline void dfs1(int x, int last) {
sz[x] = 1;
for(auto u : g[x])
if(u != last) {
fa[u] = x;
dep[u] = dep[x] + 1;
dfs1(u, x);
sz[x] += sz[u];
if(sz[son[x]] < sz[u]) son[x] = u;
}
return ;
}
inline void dfs2(int x) {
dfn[x] = ++ times;
id[dfn[x]] = x;
if(son[x]) {
top[son[x]] = top[x];
dfs2(son[x]);
}
for(auto u : g[x])
if(! top[u]) {
top[u] = u;
dfs2(u);
}
return ;
}
}
using namespace Graph;
namespace Segment_Tree {
#define mid ((L + R) >> 1)
#define son p, L, R
#define lson ls(p), L, mid
#define rson rs(p), mid + 1, R
struct Node {
int lc, rc, res, cov;
} t[N << 2];
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
inline void psup(int p) {
t[p].lc = t[ls(p)].lc;
t[p].rc = t[rs(p)].rc;
t[p].res = t[ls(p)].res + t[rs(p)].res;
if(t[ls(p)].rc == t[rs(p)].lc) ++ t[p].res;
return ;
}
inline void build(int p = 1, int L = 1, int R = n) {
t[p] = {-1, -1, 0, 0};
if(L == R) {
t[p].lc = t[p].rc = a[id[L]];
return ;
}
build(lson), build(rson), psup(p);
return ;
}
inline void work(int p, int L, int R, int k) {
t[p].res = R - L;
t[p].cov = k;
t[p].lc = t[p].rc = k;
return ;
}
inline void psd(int p, int L, int R) {
if(! t[p].cov) return ;
work(lson, t[p].cov), work(rson, t[p].cov);
t[p].cov = 0;
return ;
}
inline Node merge(Node x, Node y) {
Node z;
z.lc = x.lc;
z.rc = y.rc;
z.res = x.res + y.res;
if(x.rc == y.lc) ++ z.res;
return z;
}
inline void add(int l, int r, int k, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) {
work(son, k);
return ;
}
psd(son);
if(l <= mid) add(l, r, k, lson);
if(r > mid) add(l, r, k, rson);
psup(p);
return ;
}
inline int ask(int x, int p = 1, int L = 1, int R = n) {
if(L == R) return t[p].lc;
psd(son);
if(x <= mid) return ask(x, lson);
else return ask(x, rson);
}
inline Node query(int l, int r, int p = 1, int L = 1, int R = n) {
if(l <= L && R <= r) return t[p];
psd(son);
if(l > mid) return query(l, r, rson);
if(r <= mid) return query(l, r, lson);
return merge(query(l, r, lson), query(l, r, rson));
}
inline void Add(int x, int y, int z) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
add(dfn[top[x]], dfn[x], z);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
add(dfn[x], dfn[y], z);
return ;
}
inline int Query(int x, int y) {
int res = 0, cx = -1, cy = -1;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y), swap(cx, cy);
res += query(dfn[top[x]], dfn[x]).res;
int X = ask(dfn[x]), Y = ask(dfn[top[x]]);
if(X == cx) ++ res;
cx = Y;
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y), swap(cx, cy);
res += query(dfn[x], dfn[y]).res;
int X = ask(dfn[x]), Y = ask(dfn[y]);
if(X == cx) ++ res;
if(Y == cy) ++ res;
return res;
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace Segment_Tree;
inline void init() {
times = 0;
for(int i = 1 ; i <= n ; ++ i) {
g[i].clear();
id[i] = sz[i] = fa[i] = dep[i] = son[i] = top[i] = dfn[i] = 0;
}
return ;
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> T;
while(T --) {
init();
cin >> n >> Q;
for(int i = 1 ; i <= n ; ++ i)
a[i] = i;
for(int i = 1 ; i < n ; ++ i) {
cin >> u >> v;
g[u].pb(v), g[v].pb(u);
}
col = n;
dfs1(1, -1);
top[1] = 1;
dfs2(1);
build();
while(Q --) {
cin >> op >> u >> v;
if(op == 1) Add(u, v, ++ col);
else cout << Query(u, v) << '\n';
}
}
return 0;
}