浅谈重链剖分

浅谈重链剖分

介绍

重链剖分就是一些善(du)良(liu)出题人为了恶心我们加强难度,把序列上的问题转到树上的解决方案。具体方法就是把树分成一个个链,通过进行对链的修改实现对整棵树的修改。

步骤

DFS1

第一次深度优先搜索维护出每个节点的深度父亲,子树大小,还有你的重链要往哪一个儿子走(简称重儿子)。

因为我们要让划分出的重链数量尽可能的少,所以说我们要挑子树最大的那个儿子作为重儿子。

这样的话树上的每一条路径都能拆成不超过 条重链。

因为如果向下经过轻边的话,所在子树大小至少会除以二。

DFS2

要想在把树划分成重链,显然不止这些。

因为要对重链进行修改,所以说一条重链的节点编号必须是连续的。所以说要维护每个节点所在重链的顶端是谁(或者说是它属于哪一条重链),以及这个节点的新编号。

最后根据编号将它维护到树形数据结构上。

子树修改/查询

最简单的一集。

注意到子树内的编号都是连续的,所以直接对 进行操作。

做完了。

链上修改/查询

类似一个求 LCA 的过程。

当两个点不在同一条重链上的时候,就跳那个重链顶端深度更小的结点,对所在的重链进行修改。

跳到同一条重链直接做就行了。

时间复杂度显然是 的。

例题

P3384

P3384 【模板】重链剖分/树链剖分 - 洛谷

模板题,没啥好说的,按照上面的直接做就行了。

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
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) (x&-x)
const int N = 1e5 + 10;
int n, T, r, P, cnt, son[N], depth[N], fa[N], sz[N], id[N], top[N], w[N], c1[N], c2[N];
vector<int> g[N];
//---------这里是数据结构部分-------------
inline void add(int l, int r, int x) {
int ad1 = (l - 1) * x % P, ad2 = r *x % P;
for (int i = l; i <= n; i += lowbit(i)) c1[i] = (c1[i] + x) % P, c2[i] = (c2[i] + ad1) % P;
for (int i = r + 1; i <= n; i += lowbit(i)) c1[i] = (c1[i] - x + P) % P, c2[i] = (c2[i] - ad2 + P) % P;
}
inline int query(int x) {
int ret = 0;
for (int i = x; i; i -= lowbit(i)) ret = (ret + c1[i] * x % P) % P, ret = (ret - c2[i] + P) % P;
return ret;
}
inline int ask(int l, int r) {
return (query(r) - query(l - 1) + P) % P;
}
//---------这里是树剖部分----------
//dfs基本上都是千篇一律,直接背就行
inline void dfs1(int f, int u) {
fa[u] = f, sz[u] = 1, depth[u] = depth[f] + 1;
int tmp = -1;
for (int v : g[u]) {
if (v == f) continue;
dfs1(u, v);
sz[u] += sz[v];
if (sz[v] > tmp) tmp = sz[v], son[u] = v;
}
}
inline void dfs2(int f, int u) {
top[u] = f, id[u] = ++cnt;
add(id[u], id[u], w[u]);
if (son[u] == 0) return;
dfs2(f, son[u]);
for (int v : g[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
//这里是路径,不同的题可能略有修改
inline void addPath(int u, int v, int k) {
k %= P;
while (top[u] != top[v]) {
if (depth[top[u]] < depth[top[v]]) swap(u, v); //LCA 显然是优先跳小的
add(id[top[u]], id[u], k);
u = fa[top[u]];
}
if (depth[u] > depth[v]) swap(u, v); //深度大的在后
add(id[u], id[v], k);
}
//和addPath一样就不讲了
inline int askPath(int u, int v) {
int res = 0;
while (top[u] != top[v]) {
if (depth[top[u]] < depth[top[v]]) swap(u, v);
res = (res + ask(id[top[u]], id[u])) % P, u = fa[top[u]];
}
if (depth[u] > depth[v]) swap(u, v);
res = (res + ask(id[u], id[v])) % P;
return res;
}
//子树没啥好讲的
inline int askSon(int u) {
return ask(id[u], id[u] + sz[u] - 1);
}
inline void addSon(int u, int k) {
k %= P;
add(id[u], id[u] + sz[u] - 1, k);
}
//---------这里是主函数部分----------
signed main() {
cin >> n >> T >> r >> P;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(0, r), dfs2(r, r);
int op, u, v, k;
while (T--) {
cin >> op >> u;
if (op == 1) {
cin >> v >> k;
addPath(u, v, k);
} else if (op == 2) {
cin >> v;
cout << askPath(u, v) << endl;
} else if (op == 3) {
cin >> k;
addSon(u, k);
} else {
cout << askSon(u) << endl;
}
}
return 0;
}

P1505

P1505 [国家集训队] 旅游 - 洛谷

也是几乎板子,线段树稍微改一改。

但是这里有一个 trick 就是边权转点权。

对于每一条边,把它的边权转移到通向的深度更大的点。

这里有一个细节,进行链上修改的时候,跳到了同一条重链,这个时候深度更小的点的点权是不会进行修改的,然后就可以直接做了。

毒瘤题目的下标是从零开始的。

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
188
189
190
191
192
193
194
195
196
197
198
199
200
#include <bits/stdc++.h>
using namespace std;
#define debug cerr<<"The code runs successfully.\n";
#define endl '\n'
#define TRACE 1
#define tcout TRACE && cout
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define int long long
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
const int P = 998244353;
const int Base = 33331;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10, M = 2e6 + 10;

struct segtree {
int l, r, sum, max, min, lazy;
} s[N << 2];
pair<int, int> edge[N];
vector<pair<int, int>> g[N];
int n, q, id[N], cnt, son[N], siz[N], fa[N], w[N], top[N], dep[N], wssb[N];

inline void calc(int p) {
s[p].lazy ^= 1;
s[p].sum = -s[p].sum;
s[p].max = -s[p].max;
s[p].min = -s[p].min;
swap(s[p].max, s[p].min);
}
inline void pushup(int p) {
s[p].sum = s[lson(p)].sum + s[rson(p)].sum;
s[p].min = min(s[lson(p)].min, s[rson(p)].min);
s[p].max = max(s[lson(p)].max, s[rson(p)].max);
}
inline void pushdown(int p) {
if (!s[p].lazy) return;
calc(lson(p)), calc(rson(p));
s[p].lazy ^= 1;
}
inline void build(int p, int l, int r) {
s[p].l = l, s[p].r = r;
if (l == r) {
s[p].sum = s[p].min = s[p].max = w[l];
return;
}
int mid = l + r >> 1;
build(lson(p), l, mid);
build(rson(p), mid + 1, r);
pushup(p);
}
inline void change1(int p, int x, int k) {
if (s[p].l == s[p].r) {
s[p].sum = s[p].max = s[p].min = k;
return;
}
pushdown(p);
int mid = s[p].l + s[p].r >> 1;
if (mid >= x) change1(lson(p), x, k);
else change1(rson(p), x, k);
pushup(p);
}
inline void change2(int p, int l, int r) {
if (s[p].r < l || r < s[p].l) return;
if (s[p].l >= l && s[p].r <= r) {
calc(p);
return;
}
pushdown(p);
int mid = s[p].l + s[p].r >> 1;
change2(lson(p), l, r), change2(rson(p), l, r);
pushup(p);
}
inline int asksum(int p, int l, int r) {
if (s[p].r < l || r < s[p].l) return 0;
if (s[p].l >= l && s[p].r <= r) return s[p].sum;
pushdown(p);
return asksum(lson(p), l, r) + asksum(rson(p), l, r);
}
inline int askmax(int p, int l, int r) {
if (s[p].r < l || r < s[p].l) return -INF;
if (s[p].l >= l && s[p].r <= r) return s[p].max;
pushdown(p);
return max(askmax(lson(p), l, r), askmax(rson(p), l, r));
}
inline int askmin(int p, int l, int r) {
if (s[p].r < l || r < s[p].l) return INF;
if (s[p].l >= l && s[p].r <= r) return s[p].min;
pushdown(p);
return min(askmin(lson(p), l, r), askmin(rson(p), l, r));
}

inline void dfs1(int f, int u) {
fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
int tmp = -1;
for (auto e : g[u]) {
int v = e.first;
if (v == f) continue;
wssb[v] = e.second;
dfs1(u, v);
siz[u] += siz[v];
if (tmp < siz[v]) {
son[u] = v, tmp = siz[v];
}
}
}
inline void dfs2(int f, int u) {
id[u] = ++cnt, w[cnt] = wssb[u], top[u] = f;
if (son[u]) dfs2(f, son[u]);
for (auto e : g[u]) {
int v = e.first;
if (fa[u] == v || v == son[u]) continue;
dfs2(v, v);
}
}
inline void update1(int x, int k) {
int ccf;
if (dep[edge[x].first] > dep[edge[x].second]) ccf = edge[x].first;
else ccf = edge[x].second;
change1(1, id[ccf], k);
}
inline void update2(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
change2(1, id[top[x]], id[x]);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
if (x != y) change2(1, id[x] + 1, id[y]); //就是这里需要注意一下
}
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 += asksum(1, id[top[x]], id[x]);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
if (x != y) res += asksum(1, id[x] + 1, id[y]); //就是这里需要注意一下
return res;
}
inline int querymax(int x, int y) {
int res = -INF;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
res = max(res, askmax(1, id[top[x]], id[x]));
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
if (x != y) res = max(res, askmax(1, id[x] + 1, id[y]));
return res;
}
inline int querymin(int x, int y) {
int res = INF;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
res = min(res, askmin(1, id[top[x]], id[x]));
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
if (x != y) res = min(res, askmin(1, id[x] + 1, id[y]));
return res;
}

signed main() {
fst;
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
u++, v++;
g[u].push_back({v, w});
g[v].push_back({u, w});
edge[i] = {u, v};
}
dfs1(0, 1);
dfs2(1, 1);
build(1, 1, n);
cin >> q;
while (q--) {
string op;
int x, y;
cin >> op >> x >> y;
if (op == "C") {
update1(x, y);
} else if (op == "N") {
x++, y++;
update2(x, y);
} else if (op == "SUM") {
x++, y++;
cout << querysum(x, y) << endl;
} else if (op == "MAX") {
x++, y++;
cout << querymax(x, y) << endl;
} else {
x++, y++;
cout << querymin(x, y) << endl;
}
}
return 0;
}

P3979

P3979 遥远的国度 - 洛谷

这个 trick 是个换根。

随便找一个根节点搜出来。

换根和路径操作直接换就行了。

但是子树的话需要特殊注意一下。

分类讨论,设当前查询的子树是 。当 为根时,跑全局,当根不在 的子树中是一样的,但是根在 的子树中,要除了根方向上的子树之外的所有节点。

然后做完了。

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
#include <bits/stdc++.h>
using namespace std;
#define debug cerr<<"The code runs successfully.\n";
#define endl '\n'
#define TRACE 1
#define tcout TRACE && cout
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define int long long
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
const int P = 998244353;
const int Base = 33331;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10, M = 2e6 + 10;

vector<int> g[N];
struct segtree {
int l, r, x, lazy;
} s[N << 2];
int n, m, oldw[N], w[N], dep[N], id[N], top[N], fa[N], siz[N], son[N], rt, cnt;

inline void calc(int p, int x) {
s[p].x = x, s[p].lazy = x;
}
inline void pushup(int p) {
s[p].x = min(s[lson(p)].x, s[rson(p)].x);
}
inline void pushdown(int p) {
if (!s[p].lazy) return;
calc(lson(p), s[p].lazy), calc(rson(p), s[p].lazy);
s[p].lazy = 0;
}
inline void build(int p, int l, int r) {
s[p].l = l, s[p].r = r;
if (l == r) {
s[p].x = w[l];
return;
}
int mid = l + r >> 1;
build(lson(p), l, mid);
build(rson(p), mid + 1, r);
pushup(p);
}
inline void modify(int p, int L, int R, int x) {
if (s[p].l > R || L > s[p].r) return;
else if (s[p].l >= L && s[p].r <= R) {
calc(p, x);
return;
}
pushdown(p);
modify(lson(p), L, R, x);
modify(rson(p), L, R, x);
pushup(p);
}
inline int ask(int p, int L, int R) {
if (s[p].l > R || L > s[p].r) return INF;
else if (s[p].l >= L && s[p].r <= R) return s[p].x;
pushdown(p);
return min(ask(lson(p), L, R), ask(rson(p), L, R));
}

inline int check(int u) {//check就是检查子树性质的
if (rt == u) return 0;
else if (id[rt] <= id[u] || id[rt] > id[u] + siz[u] - 1) {
return 1;
} else return 2;
}
inline void dfs1(int f, int u) {
fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
int tmp = -1;
for (int v : g[u]) {
if (v == f) continue;
dfs1(u, v);
siz[u] += siz[v];
if (siz[v] > tmp) {
son[u] = v;
tmp = siz[v];
}
}
}
inline void dfs2(int tp, int u) {
top[u] = tp, id[u] = ++cnt, w[cnt] = oldw[u];
if (son[u]) dfs2(tp, son[u]);
for (auto v : g[u]) {
if (fa[u] == v || son[u] == v) continue;
dfs2(v, v);
}
}
inline void update(int x, int y, int k) {
//debug;
while (top[x] != top[y]) {
//debug;
if (dep[top[x]] < dep[top[y]]) swap(x, y);
modify(1, id[top[x]], id[x], k);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
modify(1, id[x], id[y], k);
}
inline int query(int u) {
int flg = check(u);
if (flg == 0) return s[1].x; //全局
else if (flg == 1) return ask(1, id[u], id[u] + siz[u] - 1); //直接做
else {
//debug;
//这里就要特殊做了,按照上面的做
int now = rt, sn = 0;
while (top[now] != top[u]) {
if (fa[top[now]] == u) {
sn = top[now];
break;
}
now = fa[top[now]];
}
if (!sn) sn = son[u];
int ans = ask(1, 1, id[sn] - 1);
if (id[sn] + siz[sn] - 1 != n) {
ans = min(ans, ask(1, id[sn] + siz[sn], n));
}
return ans;
}
}

signed main() {
fst;
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
cin >> oldw[i];
}
cin >> rt;
dfs1(0, 1);
dfs2(1, 1);
build(1, 1, n);
for (int i = 1; i <= m; i++) {
int op;
cin >> op;
if (op == 1) {
cin >> rt;
} else if (op == 2) {
int x, y, k;
cin >> x >> y >> k;
update(x, y, k);
} else {
int x;
cin >> x;
cout << query(x) << endl;
}
}
return 0;
}

P5314

P5314 [Ynoi2011] ODT - 洛谷

第一道 Ynoi。

节点值的维护是简单的,但是 小值不好做。

最暴力的做法就是对于每个儿子都开一个普通平衡树,然后只能暴力修改,查询的的话把自己和父亲放进去就做完了。

显然这是对于每个点都要修改,时间复杂度显然是不行的。

考虑重链的性质。它只有顶端一个点是轻儿子,剩下的都是重儿子。

所以如果普通平衡树只维护轻儿子,到了查询的时候把自己,父亲和重儿子加进去再弹出来就做完了。

原本是需要维护七个重儿子来卡常的,但是我写的平板电视随便卡卡就过了。

评测机波动正常,多交几发。

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
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
#define debug cerr<<"The code runs successfully.\n";
#define endl '\n'
#define TRACE 1
#define tcout TRACE && cout
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fir first
#define sec second
const int P = 998244353;
const int Base = 33331;
#ifdef int
const int INF = 0x3f3f3f3f3f3f3f3f;
#else
const int INF = 0x3f3f3f3f;
#endif
const int N = 1e6 + 10, M = 1e6 + 10;
#define re register
#define getchar getchar_unlocked
#define putchar putchar_unlocked
inline int read() {
re int x = 0;
re char ch = getchar();
while (!isdigit(ch)) {
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x ;
}

inline void write(int x) {
if (x > 9) write(x / 10);
putchar((x % 10) ^ 48);
}

struct node {
int x, id; //注意这里可能会被去重
inline friend bool operator<(node a, node b) {
if (a.x == b.x) return a.id < b.id;
return a.x < b.x;
}
};
tree<node, null_type, less<node>, rb_tree_tag, tree_order_statistics_node_update> trr[N];
vector<int> g[N];
int w[N], n, q, tr[N], fa[N], dep[N], id[N], siz[N], top[N], son[N], cnt, dfn[N], u, v, ww, x, k, op;
inline void add(int x, int k) {
for (; x <= n; x += x & -x) tr[x] += k;
}
inline int ask(int x) {
re int res = 0;
for (; x; x ^= x & -x) res += tr[x];
return res;
}
inline void dfs1(int f, int u) {
fa[u] = f, dep[u] = dep[f] + 1, siz[u] = 1;
for (int v : g[u]) {
if (v == f) continue;
dfs1(u, v);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
inline void dfs2(int t, int u) {
//cerr<<u<<endl;
id[u] = ++cnt, top[u] = t, dfn[cnt] = u;
add(id[u], w[u]);
add(id[u] + 1, -w[u]);
if (son[u]) dfs2(t, son[u]);
for (int v : g[u]) {
if (v == fa[u] || v == son[u]) continue;
//exit(0);
dfs2(v, v);
trr[u].insert({w[v], v});
}
}
inline void update(int u, int v, int k) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
trr[fa[top[u]]].erase({w[top[u]], top[u]});
add(id[top[u]], k);
add(id[u] + 1, -k);
w[top[u]] = ask(id[top[u]]);
trr[fa[top[u]]].insert({w[top[u]], top[u]});
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
add(id[u], k);
add(id[v] + 1, -k);
if (!fa[u] || dep[u] != dep[top[u]]) return; //只有轻儿子才能做
trr[fa[u]].erase({w[u], u});
w[u] = ask(id[u]);
trr[fa[u]].insert({w[u], u});
}
inline int query(int x, int k) {
//这里插入的时候一定要判断有没有,删除同理
trr[x].insert({ask(id[x]), x});
if (fa[x]) trr[x].insert({ask(id[fa[x]]), fa[x]});
if (son[x]) trr[x].insert({ask(id[son[x]]), son[x]});
int ans = (*trr[x].find_by_order(k - 1)).x;
trr[x].erase({ask(id[x]), x});
if (fa[x]) trr[x].erase({ask(id[fa[x]]), fa[x]});
if (son[x]) trr[x].erase({ask(id[son[x]]), son[x]});
return ans;
}
signed main() {
//fst;
n = read(), q = read();
for (re int i = 1; i <= n; ++i) w[i] = read();
for (re int i = 1; i < n; ++i) {
u = read(), v = read();
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(0, 1);
dfs2(1, 1);
while (q--) {
op = read();
if (op == 1) {
u = read(), v = read(), ww = read();
update(u, v, ww);
} else {
x = read(), k = read();
write(query(x, k));
putchar('\n');
}
}
return 0;
}

完。