树链剖分(重链剖分)
树的统计
题意
给定一棵树,每个节点都有一个点权,有以下三种操作:
- 把节点 u u u的权值改为 t t t
- 询问从点 u u u到点 v v v的路径上的节点的最大权值
- 询问从点 u u u到点 v v v的路径上的节点的权值和
分析
因为是在树上的相关操作,考虑树链剖分并且用线段树去维护区间的最大值和权值和,线段树需要完成的操作就是单点修改,区间查询最大值和权值和,在树链剖分中,第一个DFS需要求出每一个节点的子树大小、重儿子、父节点以及深度,第二个DFS需要求出DFS序,重儿子开头的节点,以及DFS序所对应的节点的编号
在建立线段树时,线段树维护的区间的下标并不是树中节点本身的下标,而是DFS序的标号,根据DFS序,在一条树链上的节点的标号一定是连续的,这样就可以用线段树去维护一条条树链的信息,而线段树中的操作不需要改变,只需要改变访问线段树时访问的区间即可,因为在DFS2中,树链剖分不仅记录了DFS序( l [ i ] l[i] l[i]),还记录了DFS序所对应的节点的编号( c n t [ i ] cnt[i] cnt[i]),查询操作和树链剖分找LCA一样,只不过需要边找LCA边记录路径的信息
AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int n, q, a[30010];
vector<int> G[30010];
int hs[30010], sz[30010], fa[30010], dep[30010], top[30010], l[30010], r[30010], id[30010], tot;
struct info {
int maxv, sum;
};
info operator +(const info &l, const info &r) {
return info{max(l.maxv, r.maxv), l.sum + r.sum};
}
struct node {
info val;
}seg[30010 << 2];
void update(int i) {
seg[i].val = seg[i << 1].val + seg[i << 1 | 1].val;
}
void buildtree(int i, int l, int r) {
if (l == r) {
seg[i].val = {a[id[l]], a[id[l]]};
return;
}
int mid = l + r >> 1;
buildtree(i << 1, l, mid);
buildtree(i << 1 | 1, mid + 1, r);
update(i);
}
void modify(int i, int l, int r, int x, int k) {
if (l == r) {
seg[i].val = {k, k};
return;
}
int mid = l + r >> 1;
if (x <= mid) {
modify(i << 1, l, mid, x, k);
} else {
modify(i << 1 | 1, mid + 1, r, x, k);
}
update(i);
}
info query(int i, int l, int r, int x, int y) {
if (x == l && r == y) {
return seg[i].val;
}
int mid = l + r >> 1;
if (y <= mid) {
return query(i << 1, l, mid, x, y);
} else {
if (x > mid) {
return query(i << 1 | 1, mid + 1, r, x, y);
} else {
return query(i << 1, l, mid, x, mid) + query(i << 1 | 1, mid + 1, r, mid + 1, y);
}
}
}
void dfs1(int u, int f) {
sz[u] = 1;
hs[u] = -1;
fa[u] = f;
dep[u] = dep[f] + 1;
for (auto v : G[u]) {
if (v != f) {
dfs1(v, u);
sz[u] += sz[v];
if (hs[u] == -1 || sz[v] > sz[hs[u]]) {
hs[u] = v;
}
}
}
}
void dfs2(int u, int f) {
top[u] = f;
l[u] = ++tot;
id[tot] = u;
if (hs[u] != -1) {
dfs2(hs[u], f);
}
for (auto v : G[u]) {
if (v != fa[u] && v != hs[u]) {
dfs2(v, v);
}
}
r[u] = tot;
}
info query(int x, int y) {
info ans = {-0x3f3f3f3f, 0};
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) {
ans = ans + query(1, 1, n, l[top[y]], l[y]);
y = fa[top[y]];
} else {
ans = ans + query(1, 1, n, l[top[x]], l[x]);
x = fa[top[x]];
}
}
if (dep[x] < dep[y]) {
ans = ans + query(1, 1, n, l[x], l[y]);
} else {
ans = ans + query(1, 1, n, l[y], l[x]);
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dfs1(1, 0);
dfs2(1, 1);
buildtree(1, 1, n);
cin >> q;
while (q--) {
string op;
cin >> op;
int x, y;
cin >> x >> y;
if (op[0] == 'C') {
modify(1, 1, n, l[x], y);
} else {
info ans = query(x, y);
if (op[1] == 'M') {
cout << ans.maxv << '\n';
} else {
cout << ans.sum << '\n';
}
}
}
return 0;
}
染色
题意
给定一棵树,需要完成两种操作:
- 将节点 a a a到节点 b b b的路径上的所有点(包括 a a a和 b b b)都染成颜色 c c c
- 询问节点 a a a到节点 b b b的路径上的颜色段数量
分析
依然是给定一棵树,需要完成对树上路径的操作,考虑树链剖分+线段树维护区间,根据题意可得,在线段树中我们需要维护的是区间中最左边节点的颜色、最右边节点的颜色以及不同颜色的段数,因为涉及到整个大区间的修改考虑使用懒标记的线段树维护区间,树链剖分部分的DFS1和DFS2操作不变,在线段树中每一次合并操作需要判断左儿子的右端点颜色和右儿子的左端点颜色是否一样。
因为涉及到区间修改操作,区间修改操作和区间查询操作的步骤相似,都是在找LCA的操作中边找边修改路径信息,但区间查询操作略有不同,前面所有的查询或修改操作都不需要考虑区间在左在右的问题,但在这个题的区间查询操作中需要考虑查询区间的左右问题,因为涉及到比较左区间的右端点颜色和右区间的左端点颜色,区间的左右不能颠倒,否则计算区间颜色段数就会出错,在查询时是从深节点往浅节点跳着查询,所以应该是查询的区间+ans 而不是 ans+查询的区间(!),并且节点 u u u和节点 v v v两条链一起往上跳需要分别计算两条路径的ans,不能用一个ans全部记录,根据上述操作可知,ansu记录的是LCA到u路径上的信息,ansv记录的是LCA到v路径上的信息,若要计算节点u到节点v路径上的颜色段数量,也就是计算ansu中颜色段数量+ansv中颜色段数量+(判断ansu和ansv的左端点是否相等),至于为什么要判断是因为ansu和ansv记录的都是由浅到深节点路径的信息,所以在合并的时候需要把任意一个区间反过来才是由u到v路径上的信息
AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int n, q, w[100010];
vector<int> G[100010];
int hs[100010], fa[100010], sz[100010], dep[100010], top[100010], l[100010], r[100010], id[100010], tot;
struct info {
int lc, rc, seg;
};
info operator +(const info &l, const info &r) {
return (info){l.lc, r.rc, l.seg + r.seg + (l.rc != r.lc)};
}
struct node {
info val;
int t;
}seg[100010 << 2];
void settag(int i, int t) {
seg[i].val = {t, t, 0};
seg[i].t = t;
}
void pushdown(int i) {
if (seg[i].t != 0) {
settag(i << 1, seg[i].t);
settag(i << 1 | 1, seg[i].t);
seg[i].t = 0;
}
}
void update(int i) {
seg[i].val = seg[i << 1].val + seg[i << 1 | 1].val;
}
void buildtree(int i, int l, int r) {
if (l == r) {
seg[i].val = {w[id[l]], w[id[l]], 0};
return;
}
int mid = l + r >> 1;
buildtree(i << 1, l, mid);
buildtree(i << 1 | 1, mid + 1, r);
update(i);
}
void modify(int i, int l, int r, int x, int y, int k) {
if (l == x && r == y) {
settag(i, k);
return;
}
pushdown(i);
int mid = l + r >> 1;
if (y <= mid) {
modify(i << 1, l, mid, x, y, k);
} else {
if (x > mid) {
modify(i << 1 | 1, mid + 1, r, x, y, k);
} else {
modify(i << 1, l, mid, x, mid, k);
modify(i << 1 | 1, mid + 1, r, mid + 1, y, k);
}
}
update(i);
}
info query(int i, int l, int r, int x, int y) {
if (l == x && r == y) {
return seg[i].val;
}
pushdown(i);
int mid = l + r >> 1;
if (y <= mid) {
return query(i << 1, l, mid, x, y);
} else {
if (x > mid) {
return query(i << 1 | 1, mid + 1, r, x, y);
} else {
return query(i << 1, l, mid, x, mid) + query(i << 1 | 1, mid + 1, r, mid + 1, y);
}
}
}
void dfs1(int u, int f) {
sz[u] = 1;
hs[u] = -1;
fa[u] = f;
dep[u] = dep[f] + 1;
for (auto v : G[u]) {
if (v != f) {
dfs1(v, u);
sz[u] += sz[v];
if (hs[u] == -1 || sz[v] > sz[hs[u]]) {
hs[u] = v;
}
}
}
}
void dfs2(int u, int f) {
top[u] = f;
l[u] = ++tot;
id[tot] = u;
if (hs[u] != -1) {
dfs2(hs[u], f);
}
for (auto v : G[u]) {
if (v != fa[u] && v != hs[u]) {
dfs2(v, v);
}
}
}
int query(int u, int v) {
info ansu = {0, 0, -1}, ansv = {0, 0, -1};
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) {
ansv = query(1, 1, n, l[top[v]], l[v]) + ansv;
v = fa[top[v]];
} else {
ansu = query(1, 1, n, l[top[u]], l[u]) + ansu;
u = fa[top[u]];
}
}
if (dep[u] < dep[v]) {
ansv = query(1, 1, n, l[u], l[v]) + ansv;
} else {
ansu = query(1, 1, n, l[v], l[u]) + ansu;
}
return ansu.seg + ansv.seg + (ansu.lc != ansv.lc) + 1;
}
void modify(int u, int v, int c) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) {
swap(u, v);
}
modify(1, 1, n, l[top[v]], l[v], c);
v = fa[top[v]];
}
if (dep[u] > dep[v]) {
swap(u, v);
}
modify(1, 1, n, l[u], l[v], c);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs1(1, 0);
dfs2(1, 1);
buildtree(1, 1, n);
while (q--) {
string op;
cin >> op;
if (op[0] == 'C') {
int a, b, c;
cin >> a >> b >> c;
modify(a, b, c);
} else {
int x, y;
cin >> x >> y;
cout << query(x, y) << '\n';
}
}
return 0;
}
Distance Queries on a Tree
题意
给定一棵树,需要完成以下两种操作:
- 将第 i i i条边的边权改成 w w w
- 查询 u u u到 v v v路径上的边权和
分析
经典树链剖分+线段树的边权转点权问题,处理以上问题的方法就是把边权全部转移成子节点的点权,这样除了根节点的点权为0,其他节点的点权都是边权,在查询时如果 u u u和 v v v相等,说明是不走边的,特判为0。否则,在向上找LCA的时候是相同的过程,只不过找到相同LCA后,再查询是查询的 u u u的点权到 v v v的点权这条路径上的权值和,若 u u u比 v v v深,则 v v v的点权多加了一次,否则 u u u的点权多加了一次,所以最后一次查询,需要查询 l [ 浅的 ] + 1 l[浅的]+1 l[浅的]+1到 l [ 深的 ] l[深的] l[深的],特判 l [ 浅的 ] + 1 ≤ l [ 深的 ] l[浅的]+1\leq l[深的] l[浅的]+1≤l[深的]。其他与正常树剖+线段树写法一样。
AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int n;
vector<pair<int, int> > G[200010];
struct edge {
int u, v, w;
}e[200010];
int val[200010], hs[200010], fa[200010], dep[200010], sz[200010], top[200010], l[200010], r[200010], id[200010], tot;
struct info {
LL sum;
};
info operator + (const info &l, const info &r) {
return {l.sum + r.sum};
}
struct node {
info val;
}sg[200010 << 2];
void update(int i) {
sg[i].val = sg[i << 1].val + sg[i << 1 | 1].val;
}
void buildtree(int i, int l, int r) {
if (l == r) {
sg[i].val.sum = val[id[l]];
return;
}
int mid = l + r >> 1;
buildtree(i << 1, l, mid);
buildtree(i << 1 | 1, mid + 1, r);
update(i);
}
void modify(int i, int l, int r, int x, int k) {
if (l == r) {
sg[i].val.sum = k;
return;
}
int mid = l + r >> 1;
if (x <= mid) {
modify(i << 1, l, mid, x, k);
} else {
modify(i << 1 | 1, mid + 1, r, x, k);
}
update(i);
}
LL query(int i, int l, int r, int x, int y) {
if (l == x && r == y) {
return sg[i].val.sum;
}
int mid = l + r >> 1;
if (y <= mid) {
return query(i << 1, l, mid, x, y);
} else {
if (x > mid) {
return query(i << 1 | 1, mid + 1, r, x, y);
} else {
return query(i << 1, l, mid, x, mid) + query(i << 1 | 1, mid + 1, r, mid + 1, y);
}
}
}
LL query(int u, int v) {
LL ans = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) {
ans = ans + query(1, 1, n, l[top[v]], l[v]);
v = fa[top[v]];
} else {
ans = ans + query(1, 1, n, l[top[u]], l[u]);
u = fa[top[u]];
}
}
if (dep[u] < dep[v]) {
if (l[u] + 1 <= l[v]) {
ans = ans + query(1, 1, n, l[u] + 1, l[v]);
}
} else {
if (l[v] + 1 <= l[u]) {
ans = ans + query(1, 1, n, l[v] + 1, l[u]);
}
}
return ans;
}
void dfs1(int u, int f) {
sz[u] = 1;
hs[u] = -1;
fa[u] = f;
dep[u] = dep[f] + 1;
for (auto v : G[u]) {
if (v.first != f) {
val[v.first] = v.second;
dfs1(v.first, u);
sz[u] += sz[v.first];
if (hs[u] == -1 || sz[v.first] > sz[hs[u]]) {
hs[u] = v.first;
}
}
}
}
void dfs2(int u, int f) {
top[u] = f;
l[u] = ++tot;
id[tot] = u;
if (hs[u] != -1) {
dfs2(hs[u], f);
}
for (auto v : G[u]) {
if (v.first != fa[u] && v.first != hs[u]) {
dfs2(v.first, v.first);
}
}
r[u] = tot;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i < n; i++) {
cin >> e[i].u >> e[i].v >> e[i].w;
G[e[i].u].push_back(make_pair(e[i].v, e[i].w));
G[e[i].v].push_back(make_pair(e[i].u, e[i].w));
}
dfs1(1, 0);
dfs2(1, 1);
buildtree(1, 1, n);
int q;
cin >> q;
while (q--) {
int op;
cin >> op;
if (op == 1) {
int x, w;
cin >> x >> w;
int u = e[x].u, v = e[x].v;
if (fa[u] == v) {
modify(1, 1, n, l[u], w);
} else {
modify(1, 1, n, l[v], w);
}
} else {
int x, y;
cin >> x >> y;
if (x == y) {
cout << "0\n";
} else {
cout << query(x, y) << '\n';
}
}
}
return 0;
}