洛谷P3384轻重链剖分模板题

本文详细介绍了一种处理树形结构的有效方法——轻重链剖分。通过将树形结构转化为链式结构,利用线段树等数据结构进行高效维护。文章提供了具体的实现代码和关键步骤,帮助读者理解轻重链剖分的原理及其应用。

题目链接 P3384 【模板】轻重链剖分

树剖模板题。

树剖码量真大(

树剖主要是把树形结构处理成链式结构,从而可以用诸如线段树、树状数组的数据结构进行维护。

学习树剖之前得先会线段树、了解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的位置,避免两个一起跳从而擦肩而过。

两个性质:

  1. 如果 u , v u,v u,v是一条轻边,那么有 s i z e [ u ] > = 2 ∗ s i z e [ v ] size[u]>=2*size[v] size[u]>=2size[v]。这个性质很显然,轻边的话说明 v v v u u u的轻儿子,又因为有 s i z e 重 > = s i z e 轻 size重>=size轻 size>=size,因此有此性质。
  2. 从根结点到任意结点的路所经过的轻重链的个数必定都小于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;
}

参考资料:
  1. 树链剖分详解
  2. 树链剖分原理和实现
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hesorchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值