【转载】毛毛虫剖分
转载声明
定义
一种由重链剖分推广而成的树上结点重标号方法,支持 修改/查询 一只毛毛虫的信息,并且可以对毛毛虫的身体和足分别 修改/查询 不同信息 。
可以用来解决一些大力树剖也可以解决的问题。
一些定义:
毛毛虫:一条树上的链和与这条链邻接的所有结点构成的集合;
虫身:毛毛虫的链部分;
虫足:毛毛虫除虫身的部分。
重标号方法:
首先重剖求出重链。若现在递归处理到结点 u:
若 u 还未被标号,则为其标号;
若 u 是链头,遍历这条重链,将邻接这条链的结点依次标号;
先递归重儿子,再递归轻儿子。
重标号性质:
对于重链,除链头外的结点标号连续;
对于任意结点,其轻儿子标号连续;
对于以重链头为根的子树,与这条重链邻接的所有结点标号连续;
借此我们可以很容易地求维护每条毛毛虫的信息。
同时也能顺便维护重链链分的所有信息以及子树的所有信息(一棵子树至多剖分为三个不交区间:重链区间、邻接轻点区间、邻接轻子树区间),复杂度与重链剖分完全一样。
例题:
NOI2021 轻重边
做法:对原树进行毛毛虫剖分后,要支持对一条毛毛虫身体染黑色,足部染白色,查询一条链上的黑点数量。
#include <bits/stdc++.h>
using namespace std;
int T;
int n, m;
int ver[200005], ne[200005], head[100005], tot;
inline void link(int x, int y) {
ver[++tot] = y;
ne[tot] = head[x];
head[x] = tot;
}
int siz[100005], son[100005], fa[100005], dep[100005];
void dfs1(int x, int fi) {
siz[x] = 1; fa[x] = fi; dep[x] = dep[fi] + 1;
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fi) continue;
dfs1(u, x); siz[x] += siz[u];
if (siz[u] > siz[son[x]]) son[x] = u;
}
}
int top[100005];
int dfn[100005], cnt, lein[100005], leout[100005], alout[100005], subin[100005], subout[100005];
void cover(int x) {
lein[x] = cnt + 1;
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fa[x] || u == son[x]) continue;
dfn[u] = ++cnt;
}
leout[x] = cnt;
if (son[x]) cover(son[x]);
alout[x] = cnt;
}
void dfs2(int x, int fi) {
top[x] = fi;
if (!dfn[x]) dfn[x] = ++cnt;
if (x == top[x]) cover(x);
subin[x] = cnt + 1;
if (son[x]) dfs2(son[x], fi);
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fa[x] || u == son[x]) continue;
dfs2(u, u);
}
subout[x] = cnt;
}
int tree[400005], lazy[400005];
void build(int l = 1, int r = n, int i = 1) {
tree[i] = 0; lazy[i] = -1;
if (l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
}
inline void push(int i, int l, int r) {
int mid = (l + r) >> 1;
tree[i << 1] = lazy[i] * (mid - l + 1); lazy[i << 1] = lazy[i];
tree[i << 1 | 1] = lazy[i] * (r - mid); lazy[i << 1 | 1] = lazy[i];
lazy[i] = -1;
}
void update(int fr, int to, int v, int l = 1, int r = n, int i = 1) {
if (fr > r || to < l) return ;
if (fr <= l && to >= r) {
tree[i] = (r - l + 1) * v; lazy[i] = v;
return ;
}
if (~lazy[i]) push(i, l, r);
int mid = (l + r) >> 1;
update(fr, to, v, l, mid, i << 1); update(fr, to, v, mid + 1, r, i << 1 | 1);
tree[i] = tree[i << 1] + tree[i << 1 | 1];
}
inline void lca(int x, int y) {
vector<int> vec;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
update(lein[top[x]], leout[x], 0);
update(dfn[son[x]], dfn[son[x]], 0);
if (x != top[x]) update(dfn[son[top[x]]], dfn[x], 1);
vec.push_back(top[x]);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
update(lein[y], leout[x], 0);
update(dfn[y], dfn[y], 0);
update(dfn[son[x]], dfn[son[x]], 0);
if (x != y) update(dfn[son[y]], dfn[x], 1);
for (auto it : vec) update(dfn[it], dfn[it], 1);
}
int query(int fr, int to, int l = 1, int r = n, int i = 1) {
if (fr > r || to < l) return 0;
if (fr <= l && to >= r) return tree[i];
if (~lazy[i]) push(i, l, r);
int mid = (l + r) >> 1;
return query(fr, to, l, mid, i << 1) + query(fr, to, mid + 1, r, i << 1 | 1);
}
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);
if (top[x] != x) res += query(dfn[son[top[x]]], dfn[x]);
res += query(dfn[top[x]], dfn[top[x]]);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
if (x != y) res += query(dfn[son[y]], dfn[x]);
return res;
}
inline void clear() {
for (int i = 1; i <= n; i++) head[i] = 0;
tot = 0;
for (int i = 1; i <= n; i++) siz[i] = 0;
for (int i = 1; i <= n; i++) son[i] = 0;
for (int i = 1; i <= n; i++) dfn[i] = 0;
cnt = 0;
build();
}
inline void solve() {
scanf("%d%d", &n, &m);
clear();
for (int i = 1; i < n; i++) {
int x, y; scanf("%d%d", &x, &y);
link(x, y); link(y, x);
}
dfs1(1, 1);
dfs2(1, 1);
while (m--) {
int op, x, y; scanf("%d%d%d", &op, &x, &y);
if (op == 1) lca(x, y);
else printf("%d\n", Query(x, y));
}
}
int main() {
freopen("edge.in", "r", stdin);
freopen("edge.out", "w", stdout);
scanf("%d", &T);
while (T--) solve();
return 0;
}
基础树剖练习题
题目:给定一棵 n 个点以 1 为根的有根树,边有颜色,初始时全为白色。
接下来有三种操作:
1 u 表示修改 u 到根路径上的边的颜色,具体修改方式在下文说明。
2 u 表示查询 u 到根路径上的黑边的个数。
3 u 表示查询 u 子树内黑边的个数。
修改方式:首先,将从 u 到根的链拉出来,即为 lis1,lis2,...,lism,其中 lisi=u,lism=1,∀1≤i<m lisi+1=falsti。
然后,除去 u 到根路径上的边,将其余与 lis1,lis3,lis5,... 相连的边染成黑色,将与 lis2,lis4,lis6,... 相连的边染成白色。
最后,对于在 u 到根路径上的边,将 (lis1,lis2),(lis3,lis4),(lis5,lis6),... 染成黑色, 将 (lis2,lis3),(lis4,lis5),(lis6,lis7),... 染成白色。
做法:对原树进行剖分后,对于 1 操作,我们要对毛毛虫身体上与 x 深度奇偶性相同的点染黑,不同的染白,足部奇偶性不同的点染黑,相同的染白,2,3 操作查询在链上和子树内的黑点个数即可。
#include <bits/stdc++.h>
using namespace std;
int n, q;
int ver[200005], ne[200005], head[100005], tot;
inline void link(int x, int y) {
ver[++tot] = y;
ne[tot] = head[x];
head[x] = tot;
}
int siz[100005], son[100005], fa[100005], dep[100005];
void dfs1(int x, int fi) {
siz[x] = 1;
dep[x] = dep[fi] + 1;
fa[x] = fi;
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fi) continue;
dfs1(u, x); siz[x] += siz[u];
if (siz[u] > siz[son[x]]) son[x] = u;
}
}
int dfn[100005], cnt, top[100005];
int lein[100005], leout[100005], subin[100005], subout[100005], alout[100005];
void cover(int x) {
lein[x] = cnt + 1;
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fa[x] || u == son[x]) continue;
dfn[u] = ++cnt;
}
leout[x] = cnt;
if (son[x]) cover(son[x]);
alout[x] = cnt;
}
void dfs2(int x, int fi) {
top[x] = fi;
if (!dfn[x]) dfn[x] = ++cnt;
if (x == top[x]) cover(x);
subin[x] = cnt + 1;
if (son[x]) dfs2(son[x], fi);
for (int i = head[x]; i; i = ne[i]) {
int u = ver[i];
if (u == fa[x] || u == son[x]) continue;
dfs2(u, u);
}
subout[x] = cnt;
}
int tree[3][400005], lazy[400005];
void insert(int loc, int v, int l = 1, int r = n, int i = 1) {
if (loc < l || loc > r) return;
lazy[i] = -1;
if (l == r) {
tree[v][i] = 1;
return;
}
int mid = (l + r) >> 1;
insert(loc, v, l, mid, i << 1);
insert(loc, v, mid + 1, r, i << 1 | 1);
tree[0][i] = tree[0][i << 1] + tree[0][i << 1 | 1];
tree[1][i] = tree[1][i << 1] + tree[1][i << 1 | 1];
}
inline void push(int i) {
tree[2][i << 1] = tree[lazy[i]][i << 1];
lazy[i << 1] = lazy[i];
tree[2][i << 1 | 1] = tree[lazy[i]][i << 1 | 1];
lazy[i << 1 | 1] = lazy[i];
lazy[i] = -1;
}
void update(int fr, int to, int v, int l = 1, int r = n, int i = 1) {
if (fr > r || to < l) return;
if (fr <= l && to >= r) {
tree[2][i] = tree[v][i]; lazy[i] = v;
return;
}
if (~lazy[i]) push(i);
int mid = (l + r) >> 1;
update(fr, to, v, l, mid, i << 1);
update(fr, to, v, mid + 1, r, i << 1 | 1);
tree[2][i] = tree[2][i << 1] + tree[2][i << 1 | 1];
}
int query(int fr, int to, int l = 1, int r = n, int i = 1) {
if (fr > r || to < l) return 0;
if (fr <= l && to >= r) return tree[2][i];
if (~lazy[i]) push(i);
int mid = (l + r) >> 1;
return query(fr, to, l, mid, i << 1) + query(fr, to, mid + 1, r, i << 1 | 1);
}
inline void lca(int x, int y, int v) {
vector<int> vec;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
update(lein[top[x]], leout[x], v ^ 1);
update(dfn[son[x]], dfn[son[x]], v ^ 1);
if (x != top[x]) update(dfn[son[top[x]]], dfn[x], v);
vec.push_back(top[x]); x = fa[top[x]];
}
if (dep[y] > dep[x]) swap(x, y);
update(lein[y], leout[x], v ^ 1);
update(dfn[son[x]], dfn[son[x]], v ^ 1);
if (x != y) update(dfn[son[y]], dfn[x], v);
for (auto it : vec) update(dfn[it], dfn[it], v);
}
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);
if (x != top[x]) res += query(dfn[son[top[x]]], dfn[x]);
res += query(dfn[top[x]], dfn[top[x]]); x = fa[top[x]];
}
if (dep[y] > dep[x]) swap(x, y);
if (x != y) res += query(dfn[son[y]], dfn[x]);
return res;
}
int main() {
freopen("chain.in", "r", stdin);
freopen("chain.out", "w", stdout);
scanf("%d", &n);
for (int i = 2; i <= n; i++) {
int x; scanf("%d", &x);
link(i, x); link(x, i);
}
dfs1(1, 1);
dfs2(1, 1);
for (int i = 2; i <= n; i++) insert(dfn[i], dep[i] & 1);
scanf("%d", &q);
while (q--) {
int op, x;
scanf("%d%d", &op, &x);
if (op == 1)
lca(1, x, dep[x] & 1);
if (op == 2)
printf("%d\n", Query(1, x));
if (op == 3)
printf("%d\n", query(lein[x], alout[x]) + query(subin[x], subout[x]));
}
return 0;
}