【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 之间。
【题目分析】
模板题

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<cmath> using namespace std; const int N = 30050; 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 << 1], nxt[N << 1], tot; int sum[N * 4], maxx[N * 4]; int n, q; inline int Re(){ int i = 0, f = 1; char ch = getchar (); for (; (ch < '0' || ch > '9' ) && ch != '-' ; ch = getchar ()); if (ch == '-' ) f = -1, ch = getchar (); for (; ch >= '0' && ch <= '9' ; ch = getchar ()) i = (i << 3) + (i << 1) + (ch - '0' ); return i * f; } inline void Wr( int x){ if (x < 0) putchar ( '-' ), x = -x; if (x > 9) Wr(x / 10); putchar (x % 10 + '0' ); } 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] + 1; fa[u] = f; sze[u] = 1; 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 >> 1, lc = k << 1, rc = k << 1 | 1; build(lc, l, mid); build(rc, mid + 1, 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 >> 1, lc = k << 1, rc = k << 1 | 1; int ret = 0; if (x <= mid) ret += PathSum(lc, l, mid, x, y); if (y > mid) ret += PathSum(rc, mid + 1, 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 >> 1, lc = k << 1, rc = k << 1 | 1; int ret = -oo; if (x <= mid) ret = chkMax(ret, PathMax(lc, l, mid, x, y)); if (y > mid) ret = chkMax(ret, PathMax(rc, mid + 1, r, x, y)); return ret; } inline void PrintSum( int u, int v){ int ret = 0; while (top[u] != top[v]){ if (dep[top[u]] < dep[top[v]]) swap(u, v); ret += PathSum(1, 1, n, pos[top[u]], pos[u]); u = fa[top[u]]; } if (dep[u] > dep[v]) swap(u, v); ret += PathSum(1, 1, 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(1, 1, n, pos[top[u]], pos[u])); u = fa[top[u]]; } if (dep[u] > dep[v]) swap(u, v); ret = chkMax(ret, PathMax(1, 1, 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 >> 1, lc = k << 1, rc = k << 1 | 1; if (pos <= mid) modify(lc, l, mid, pos, v); else modify(rc, mid + 1, r, pos, v); sum[k] = sum[lc] + sum[rc]; maxx[k] = chkMax(maxx[lc], maxx[rc]); } inline void print( int k){ if (k == 0) return ; print(k<<1);print(k<<1|1); cout<<sum[k]<< " " <<maxx[k]<<endl; } int main(){ // freopen("h.in", "r", stdin); n = Re(); for ( int i = 1; i < n; i++){ int a = Re(), b = Re(); addEdge(a, b); } for ( int i = 1; i <= n; i++) val[i] = Re(); dep[0] = -1, top[1] = 1, idx[1] = 1, pos[1] = 1, tot = 1; dfs1(1, 0); dfs2(1, 0); build(1, 1, n); // print(1); q = Re(); for ( int i = 1; i <= q; i++){ char opt[20]; int u, v, t; scanf ( "%s" , opt + 1); if (opt[2] == 'H' ){ //change u = Re(), t = Re(); modify(1, 1, n, pos[u], t); } else if (opt[2] == '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, 数组线段树不好维护可以写成结构体!!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #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 << 1], nxt[N << 1]; inline int Re(){ int i = 0, f = 1; char ch = getchar (); for (; (ch < '0' || ch > '9' ) && ch != '-' ; ch = getchar ()); if (ch == '-' ) f = -1, ch = getchar (); for (; ch >= '0' && ch <= '9' ; ch = getchar ()) i = (i << 3) + (i << 1) + (ch - '0' ); return i * f; } inline void Wr( int x){ if (x < 0) putchar ( '-' ), x = -x; if (x > 9) Wr(x / 10); putchar (x % 10 + '0' ); } 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] + 1; sze[u] = 1; 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(0), lcol(-1), rcol(-1), tag(-1){} }; namespace SegTree{ node tr[N * 4]; inline void upt( int k){ int lc = k << 1, rc = k << 1 | 1; 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 = 1; tr[k].tag = v; } inline void pushDown( int k){ int lc = k << 1, rc = k << 1 | 1; if (tr[k].tag != -1){ cover(lc, tr[k].tag); cover(rc, tr[k].tag); tr[k].cnt = 1, tr[k].lcol = tr[k].rcol = tr[k].tag; tr[k].tag = -1; } } inline void build( int k, int l, int r){ if (l == r){ tr[k].cnt = 1; tr[k].tag = -1; tr[k].lcol = tr[k].rcol = val[idx[l]]; return ; } int mid = l + r >> 1, lc = k << 1, rc = k << 1 | 1; build(lc, l, mid); build(rc, mid + 1, 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 >> 1, lc = k << 1, rc = k << 1 | 1; if (x <= mid) modify(lc, l, mid, x, y, v); if (y > mid) modify(rc, mid + 1, 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 >> 1, lc = k << 1, rc = k << 1 | 1; if (y <= mid) return query(lc, l, mid, x, y); else if (x > mid) return query(rc, mid + 1, r, x, y); else { node ret, ret1, ret2; ret1 = query(lc, l, mid, x, mid); ret2 = query(rc, mid + 1, r, mid + 1, 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 = 0, acol = -1, bcol = -1; while (top[a] != top[b]){ if (dep[top[a]] < dep[top[b]]) swap(a, b), swap(acol, bcol); node ret = query(1, 1, 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(1, 1, 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(1, 1, n, pos[top[a]], pos[a], v); a = fa[top[a]]; } if (dep[a] > dep[b]) swap(a, b); modify(1, 1, n, pos[a], pos[b], v); } int main(){ freopen ( "h.in" , "r" , stdin); n = Re(), m = Re(); for ( int i = 1; i <= n; i++) val[i] = Re(); for ( int i = 1; i < n; i++){ int a = Re(), b = Re(); addEdge(a, b); } dep[0] = -1, top[1] = pos[1] = idx[1] = tot = 1; dfs1(1, 0); dfs2(1, 0); build(1, 1, n); for ( int i = 1; 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 0; } |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步