【知识】树链剖分
树链剖分
思想:
将一颗树转换成一段序列,满足树中任意一条路径 $\Leftrightarrow $ 不超过 \(\log n\) 段区间
概念:
- 重儿子
一个点的重儿子为它的儿子的子树节点个数最多的那个点。
如有多个,任选一个。
-
轻儿子
不是重儿子的都为轻儿子
-
重边
与重儿子相连的边
-
轻边
与轻儿子相连的边
-
重链
极大的由重边构成的链
-
轻链
极大的由轻边构成的链
变树为序:
优先遍历重儿子的 DFS序,这样可以保证所有重链上的所有点是有序的。
定理:
树中任意一条路径可以拆分成 \(\log n\) 条重链,即可拆分成 \(\log n\) 个连续区间。
时间复杂度:
单次询问:\(\mathcal{O}(\log ^2 n)\)
模板:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, M = N * 2;
int n, m;
int w[N], h[N], e[M], ne[M], idx;
int id[N], nw[N], cnt;
int dep[N], sz[N], top[N], fa[N], son[N];
struct Tree
{
int l, r;
LL add, sum;
}tr[N * 4];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u, int father, int depth)
{
dep[u] = depth, fa[u] = father, sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
dfs1(j, u, depth + 1);
sz[u] += sz[j];
if (sz[son[u]] < sz[j]) son[u] = j;
}
}
void dfs2(int u, int t)
{
id[u] = ++ cnt, nw[cnt] = w[u], top[u] = t;
if (!son[u]) return;
dfs2(son[u], t);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa[u] || j == son[u]) continue;
dfs2(j, j);
}
}
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add)
{
left.add += root.add, left.sum += root.add * (left.r - left.l + 1);
right.add += root.add, right.sum += root.add * (right.r - right.l + 1);
root.add = 0;
}
}
void build(int u, int l, int r)
{
tr[u] = {l, r, 0, nw[r]};
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void update(int u, int l, int r, int k)
{
if (l <= tr[u].l && r >= tr[u].r)
{
tr[u].add += k;
tr[u].sum += k * (tr[u].r - tr[u].l + 1);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u << 1, l, r, k);
if (r > mid) update(u << 1 | 1, l, r, k);
pushup(u);
}
LL query(int u, int l, int r)
{
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid) res += query(u << 1, l, r);
if (r > mid) res += query(u << 1 | 1, l, r);
return res;
}
void update_path(int u, int v, int k)
{
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
update(1, id[v], id[u], k);
}
LL query_path(int u, int v)
{
LL res = 0;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
res += query(1, id[top[u]], id[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
res += query(1, id[v], id[u]);
return res;
}
void update_tree(int u, int k)
{
update(1, id[u], id[u] + sz[u] - 1, k);
}
LL query_tree(int u)
{
return query(1, id[u], id[u] + sz[u] - 1);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs1(1, -1, 1);
dfs2(1, 1);
build(1, 1, n);
scanf("%d", &m);
while (m -- )
{
int t, u, v, k;
scanf("%d%d", &t, &u);
if (t == 1)
{
scanf("%d%d", &v, &k);
update_path(u, v, k);
}
else if (t == 2)
{
scanf("%d", &k);
update_tree(u, k);
}
else if (t == 3)
{
scanf("%d", &v);
printf("%lld\n", query_path(u, v));
}
else printf("%lld\n", query_tree(u));
}
return 0;
}
P2146 [NOI2015] 软件包管理器
题意:
- 安装 \(x\),将 \(1 \to x\) 上的点变成 \(1\)
- 卸载 \(x\),将以 \(x\) 为根的子树全部变成 \(0\)
树链剖分,启动!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int id[N], cnt;
int dep[N], sz[N], top[N], fa[N], son[N];
struct Tree
{
int l, r, flag, sum;
}tr[N * 4];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u, int depth)
{
dep[u] = depth, sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs1(j, depth + 1);
sz[u] += sz[j];
if (sz[son[u]] < sz[j]) son[u] = j;
}
}
void dfs2(int u, int t)
{
id[u] = ++ cnt, top[u] = t;
if (!son[u]) return;
dfs2(son[u], t);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == son[u]) continue;
dfs2(j, j);
}
}
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.flag != -1)
{
left.sum = root.flag * (left.r - left.l + 1);
right.sum = root.flag * (right.r - right.l + 1);
left.flag = right.flag = root.flag;
root.flag = -1;
}
}
void build(int u, int l, int r)
{
tr[u] = {l, r, -1, 0};
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void update(int u, int l, int r, int k)
{
if (l <= tr[u].l && r >= tr[u].r)
{
tr[u].flag = k;
tr[u].sum = k * (tr[u].r - tr[u].l + 1);
return;
}
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= mid) update(u << 1, l, r, k);
if (r > mid) update(u << 1 | 1, l, r, k);
pushup(u);
}
void update_path(int u, int v, int k)
{
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
update(1, id[v], id[u], k);
}
void update_tree(int u, int k)
{
update(1, id[u], id[u] + sz[u] - 1, k);
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for (int i = 2; i <= n; i ++ )
{
int p;
scanf("%d", &p);
p ++ ;
add(p, i);
fa[i] = p;
}
dfs1(1, 1);
dfs2(1, 1);
build(1, 1, n);
scanf("%d", &m);
char op[20];
int x;
while (m -- )
{
scanf("%s%d", op, &x);
x ++ ;
if (!strcmp(op, "install"))
{
int t = tr[1].sum;
update_path(1, x, 1);
printf("%d\n", tr[1].sum - t);
}
else
{
int t = tr[1].sum;
update_tree(x, 0);
printf("%d\n", t - tr[1].sum);
}
}
return 0;
}