【树链剖分】洛谷 P3384 【模板】树链剖分

本文详细介绍了树链剖分算法的应用场景及实现细节,通过一个具体的树形结构问题,展示了如何利用树链剖分进行高效的区间更新和查询操作。文章包含完整的C++代码示例,适合有一定基础的读者学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Step1 Problem:

题目
给你一棵n个点树,m个操作,R是根,结果取模MOD.
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

Step2 Involving algorithms:

树链剖分

Step3 Ideas:

参考大牛博客学习的

Step4 Code:

#include<bits/stdc++.h>
using namespace std;
#define lson root<<1
#define rson root<<1|1
#define MID int mid = (l+r)/2
const int N = 1e5+100;
vector<int> Map[N];
int w[N], wt[N], n, Root, MOD;
//w[i] i点的初始权值,wt[i] 映射后的点 初始权值
int fq[N], dep[N], siz[N], son[N], idx[N], top[N], cnt;
//fq[i] 点i的父亲,dep[i] 点i的深度,siz[i] 点i的儿子数(包括自己)
//son[i] 点i的重儿子, idx[i] 点i映射后的点,top[i] 该树链的根
int a[N<<2], lazy[N<<2];//维护区间和,和lazy标记
void Merge(int root)
{
    lazy[root] = 0;
    a[root] = (a[lson] + a[rson])%MOD;
}
void build(int root, int l, int r)
{
    if(l == r)
    {
        a[root] = wt[l]%MOD;
        return ;
    }
    MID;
    build(lson, l, mid);
    build(rson, mid+1, r);
    Merge(root);
}
void pushdown(int root, int l, int r)
{
    if(lazy[root])
    {
        MID;
        a[lson] += (mid-l+1)*lazy[root];
        a[lson] %= MOD;
        a[rson] += (r-(mid+1)+1)*lazy[root];
        a[rson] %= MOD;
        lazy[lson] += lazy[root];
        lazy[lson] %= MOD;
        lazy[rson] += lazy[root];
        lazy[rson] %= MOD;
        lazy[root] = 0;
    }
}
void updata(int root, int l, int r, int ul, int ur, int v)
{
    if(ul <= l && r <= ur)
    {
        a[root] += (r-l+1)*v;
        a[root] %= MOD;
        lazy[root] += v;
        lazy[root] %= MOD;
        return ;
    }
    pushdown(root, l, r);
    MID;
    if(mid >= ul)
        updata(lson, l, mid, ul, ur, v);
    if(mid < ur) updata(rson, mid+1, r, ul, ur, v);
    Merge(root);
}
int query(int root, int l, int r, int ul, int ur)
{
    if(ul <= l && r <= ur)
    {
        return a[root];
    }
    pushdown(root, l, r);
    MID;
    int res = 0;
    if(mid >= ul)
        res += query(lson, l, mid, ul, ur), res %= MOD;
    if(mid < ur) res += query(rson, mid+1, r, ul, ur), res %= MOD;
    return res;
}
//以上都是线段树区间更新的代码,不会的话,请先学会。
void dfs1(int u, int f, int deep)//第一遍dfs,求fq[],dep[],siz[],son[]
{
    fq[u] = f; dep[u] = deep;
    siz[u] = 1; son[u] = -1;
    int len = Map[u].size();
    for(int i = 0; i < len; i++)
    {
        int to = Map[u][i];
        if(to != f) {
            dfs1(to, u, deep+1);
            siz[u] += siz[to];
            if(son[u] == -1 || siz[son[u]] < siz[to])
                son[u] = to;
        }
    }
}
void dfs2(int u, int Top)//第二遍dfs,求idx[], top[]
{
    idx[u] = cnt; top[u] = Top;
    wt[cnt++] = w[u];
    if(son[u] != -1)
    dfs2(son[u], Top);
    int len = Map[u].size();
    for(int i = 0; i < len; i++)
    {
        int to = Map[u][i];
        if(to != fq[u] && son[u] != to)
        {
            dfs2(to, to);
        }
    }
}
void add(int u, int v)
{
    Map[u].push_back(v);
    Map[v].push_back(u);
}
void updata_path(int x, int y, int z)//更新x->y的路径点权
{
    int fx = top[x], fy = top[y];//树链根
    while(fx != fy)//不在一条链上
    {
        if(dep[fx] < dep[fy])
            swap(fx, fy), swap(x, y);
        updata(1, 1, n, idx[fx], idx[x], z);//更新树链根 深度最大的链
        x = fq[fx];//更新后 移动到树链根的父亲节点
        fx = top[x], fy = top[y];
    }
    //在同一条链上,直接更新就好了
    if(idx[x] > idx[y]) swap(x, y);
    updata(1, 1, n, idx[x], idx[y], z);
}
int query_path(int x, int y)//求x->y路径点权和,跟上面思路一样
{
    int fx = top[x], fy = top[y];
    int res = 0;
    while(fx != fy)
    {
        if(dep[fx] < dep[fy])
            swap(fx, fy), swap(x, y);
        res += query(1, 1, n, idx[fx], idx[x]), res %= MOD;
        x = fq[fx];
        fx = top[x], fy = top[y];
    }
    if(idx[x] > idx[y]) swap(x, y);
    res += query(1, 1, n, idx[x], idx[y]), res %= MOD;
    return res;
}
void updata_tree(int x, int z)//用到dfs序思想,子树的结点编号是连续的
{
    updata(1, 1, n, idx[x], idx[x]+siz[x]-1, z);
}
int query_tree(int x)
{
    return query(1, 1, n, idx[x], idx[x]+siz[x]-1);
}
void init()
{
    Root = Root, cnt = 1;
}
int main()
{
    int u, v, m;
    cin >> n >> m >> Root >> MOD;
    for(int i = 1; i <= n; i++)
        scanf("%d", w+i);
    for(int i = 1; i < n; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    init();
    dfs1(Root, -1, 1);
    dfs2(Root, Root);
    build(1, 1, n);
    int ok, x, y, z;
    while(m--)
    {
        scanf("%d", &ok);
        if(ok == 1)
        {
            cin >> x >> y >> z;
            updata_path(x, y, z);
        }
        else if(ok == 2)
        {
            cin >> x >> y;
            printf("%d\n", query_path(x, y));
        }
        else if(ok == 3)
        {
            cin >> x >> z;
            updata_tree(x, z);
        }
        else
        {
            cin >> x;
            printf("%d\n", query_tree(x));
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值