树剖模板题。
树剖码量真大(
树剖主要是把树形结构处理成链式结构,从而可以用诸如线段树、树状数组的数据结构进行维护。
学习树剖之前得先会线段树、了解LCA以及树型结构的一些专有名词:树链剖分专有名词图文讲解
1.首先是一个dfs函数,处理出结点的父亲、重儿子、深度、子树重量。非常简单:
void dfs1(long long now, long long fa, long long dep)
{
father[now] = fa;
depth[now] = dep;
size[now] = 1;
long long maxx = 0;
for (long long i = head[now]; i; i = e[i].next)
if (fa != e[i].v)
{
dfs1(e[i].v, now, dep + 1);
size[now] += size[e[i].v];
if (size[e[i].v] > size[son[now]])
son[now] = e[i].v;
}
}
2.然后是另一个dfs函数,用于处理每个结点的dfs序和重链。
void dfs2(long long now, long long tp)
{
dfsrank[now] = ++cnt; //dfs序,也是作为线段树处理的下标
ww[cnt] = aa[now];
top[now] = tp;
if (!son[now]) //如果是叶节点,那么直接返回
return;
dfs2(son[now], tp); //重儿子的top是now结点的top
for (long long i = head[now]; i; i = e[i].next) //处理轻儿子的top
if (e[i].v != son[now] && e[i].v != father[now])
dfs2(e[i].v, e[i].v);
}
建线段树的时候,我们是用每个节点的dfs序作为结点编号的(而不是结点原始编号),因为经过两边处理之后,有如下性质:
- 所有子树的dfs序编号都是连续的
- 每条重链上的dfs序编号都是连续的
有了上面两条性质,我们才能用数据结构进行维护。
下面是转自参考资料的一段话
回顾上文的那个题目,修改和查询操作原理是类似的,以查询操作为例,其实就是个LCA,不过这里使用了top来进行加速,因为top可以直接跳转到该重链的起始结点,轻链没有起始结点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到top的位置,避免两个一起跳从而擦肩而过。
两个性质:
- 如果 u , v u,v u,v是一条轻边,那么有 s i z e [ u ] > = 2 ∗ s i z e [ v ] size[u]>=2*size[v] size[u]>=2∗size[v]。这个性质很显然,轻边的话说明 v v v是 u u u的轻儿子,又因为有 s i z e 重 > = s i z e 轻 size重>=size轻 size重>=size轻,因此有此性质。
- 从根结点到任意结点的路所经过的轻重链的个数必定都小于logn。
不会证明,此性质可以证明树剖的时间复杂度为 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn) 。
AC代码:
long long n, m, r, q;
long long aa[100010];
struct node
{
long long v, w, next;
} e[200010];
long long depth[100010];
long long top[100010];
long long father[100010];
long long size[100010];
long long dfsrank[100010];
long long son[100010];
long long ww[100010];
long long cnt;
long long ct = 1;
long long head[100010];
void add(long long u, long long v)
{
e[ct].v = v;
e[ct].next = head[u];
head[u] = ct++;
}
void dfs1(long long now, long long fa, long long dep) //处理出结点的父亲、重儿子、深度、子树重量
{
father[now] = fa;
depth[now] = dep;
size[now] = 1;
long long maxx = 0;
for (long long i = head[now]; i; i = e[i].next)
if (fa != e[i].v)
{
dfs1(e[i].v, now, dep + 1);
size[now] += size[e[i].v];
if (size[e[i].v] > size[son[now]])
son[now] = e[i].v;
}
}
void dfs2(long long now, long long tp) //处理出重链以及各结点的dfs序
{
dfsrank[now] = ++cnt; //dfs序,也是作为线段树处理的下标
ww[cnt] = aa[now];
top[now] = tp;
if (!son[now]) //如果是叶节点,那么直接返回
return;
dfs2(son[now], tp); //重儿子的top是now结点的top
for (long long i = head[now]; i; i = e[i].next) //处理轻儿子的top
if (e[i].v != son[now] && e[i].v != father[now])
dfs2(e[i].v, e[i].v);
}
//线段树模板----------------
struct node2
{
long long l, r, k, sum, lazy;
} tr[400010];
void pushup(long long k)
{
tr[k].sum = (tr[k * 2].sum + tr[k * 2 + 1].sum) % q;
}
void build(long long k, long long l, long long r)
{
tr[k].l = l;
tr[k].r = r;
if (l == r)
{
tr[k].sum = ww[l];
return;
}
long long mid = l + r >> 1;
build(k * 2, l, mid);
build(k * 2 + 1, mid + 1, r);
pushup(k);
}
void pushdown(long long k)
{
if (tr[k].lazy)
{
tr[k * 2].sum = (tr[k].lazy * (tr[k * 2].r - tr[k * 2].l + 1) + tr[k * 2].sum) % q;
tr[k * 2 + 1].sum = (tr[k].lazy * (tr[k * 2 + 1].r - tr[k * 2 + 1].l + 1) + tr[k * 2 + 1].sum) % q;
tr[k * 2 + 1].lazy = (tr[k * 2 + 1].lazy + tr[k].lazy) % q;
tr[k * 2].lazy = (tr[k * 2].lazy + tr[k].lazy) % q;
tr[k].lazy = 0;
}
}
void change(long long k, long long l, long long r, long long w)
{
if (tr[k].l == l && tr[k].r == r)
{
tr[k].sum = (tr[k].sum + w * (tr[k].r - tr[k].l + 1)) % q;
tr[k].lazy += w;
return;
}
pushdown(k);
long long mid = (tr[k].l + tr[k].r) >> 1;
if (r <= mid)
change(k * 2, l, r, w);
else if (l > mid)
change(k * 2 + 1, l, r, w);
else
{
change(k * 2, l, mid, w);
change(k * 2 + 1, mid + 1, r, w);
}
pushup(k);
}
long long query(long long k, long long l, long long r)
{
if (tr[k].l == l && tr[k].r == r)
return tr[k].sum;
pushdown(k);
long long mid = (tr[k].l + tr[k].r) >> 1;
if (r <= mid)
return query(k * 2, l, r);
else if (l > mid)
return query(k * 2 + 1, l, r);
else
return (query(k * 2, l, mid) + query(k * 2 + 1, mid + 1, r)) % q;
}
//线段树模板------------
int main()
{
scanf("%lld%lld%lld%lld", &n, &m, &r, &q);
for (long long i = 1; i <= n; i++)
scanf("%lld", &aa[i]);
for (long long i = 1; i < n; i++)
{
long long u, v;
scanf("%lld%lld", &u, &v);
add(u, v);
add(v, u);
}
dfs1(r, 0, 1);
dfs2(r, r);
build(1, 1, n);
for (long long i = 1; i <= m; i++)
{
long long opt;
scanf("%lld", &opt);
if (opt == 1)
{
long long x, y, z;
scanf("%lld%lld%lld", &x, &y, &z);
while (top[x] != top[y]) //如果两个点所在链的顶点不一样,将深度较大的结点处理到顶点
{
if (depth[top[x]] > depth[top[y]])
{
change(1, dfsrank[top[x]], dfsrank[x], z);
x = father[top[x]];
}
else
{
change(1, dfsrank[top[y]], dfsrank[y], z);
y = father[top[y]];
}
}
//直到两个点在同一条链上,修改这两个点之间的子链
if (depth[x] > depth[y])
swap(x, y);
change(1, dfsrank[x], dfsrank[y], z);
}
else if (opt == 2)
{
long long ans = 0;
long long x, y;
scanf("%lld%lld", &x, &y);
while (top[x] != top[y])
{
if (depth[top[x]] > depth[top[y]])
{
ans = (ans + query(1, dfsrank[top[x]], dfsrank[x])) % q;
x = father[top[x]];
}
else
{
ans = (ans + query(1, dfsrank[top[y]], dfsrank[y])) % q;
y = father[top[y]];
}
}
if (depth[x] > depth[y])
swap(x, y);
ans = (ans + query(1, dfsrank[x], dfsrank[y])) % q;
printf("%lld\n", ans);
}
else if (opt == 3)
{
long long x, z;
scanf("%lld%lld", &x, &z);
change(1, dfsrank[x], dfsrank[x] + size[x] - 1, z);
}
else if (opt == 4)
{
long long x;
scanf("%lld", &x);
printf("%lld\n", query(1, dfsrank[x], dfsrank[x] + size[x] - 1));
}
}
return 0;
}
本文详细介绍了一种处理树形结构的有效方法——轻重链剖分。通过将树形结构转化为链式结构,利用线段树等数据结构进行高效维护。文章提供了具体的实现代码和关键步骤,帮助读者理解轻重链剖分的原理及其应用。
2752





