洛谷 P3384 【模板】树链剖分

本文详细解析洛谷P3384【模板】树链剖分题目,介绍如何通过树链剖分算法处理树形结构上的操作,包括路径更新和查询、子树更新和查询等,利用线段树优化复杂度。

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

洛谷 P3384 【模板】树链剖分
已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
ans:
2
21

#include<bits/stdc++.h>
#define M 100010
using namespace std;
int n,m,r,mod,cnt;
int A[M],f[M],deep[M],se[M],zson[M],mark[M],rev[M],top[M];
//A原数组,f父亲节点,deep深度,se子节点个数,zson重儿子,mark搜索编号,rev编号对应点下标,top顶端
struct node2
{
    int l,r,w,lazy;
} T[4*M];
vector<int> edge[M];
//标记每个点的深度deep[]
//标记每个点的父亲f[]
//标记每个非叶子节点的子树大小(含它自己)
//标记每个非叶子节点的重儿子编号zson
void dfs1(int u,int fa,int dep)
{

    f[u]=fa;
    deep[u]=dep;
    se[u]=1;
    int max_son=-1;
    for(auto v:edge[u])
    {
        if(v==fa) continue;
        dfs1(v,u,dep+1);
        se[u]+=se[v];
        if(se[v]>max_son) max_son=se[v],zson[u]=v;
    }
}
//标记每个点的新编号
//处理每个点所在链的顶端
//处理每条链
void dfs2(int u,int topm)
{
    mark[u]=++cnt;
    rev[cnt]=u;
    top[u]=topm;
    if(!zson[u]) return ;
    dfs2(zson[u],topm);
    for(auto v:edge[u])
    {
        if(v==f[u]||v==zson[u]) continue;
        dfs2(v,v);
    }
}
/********线段树********/
void build(int k,int l,int r)
{
    T[k].l=l,T[k].r=r,T[k].lazy=0;
    if(l==r)
    {
        T[k].w=A[rev[l]]%mod;
//        printf("-->%d %d\n",l,T[k].w);
        return ;
    }
    int mid=(l+r)>>1;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    T[k].w=(T[2*k].w+T[2*k+1].w)%mod;
}
void down(int k)
{
    T[2*k].lazy+=T[k].lazy;
    T[2*k+1].lazy+=T[k].lazy;
    T[2*k].w+=T[k].lazy*(T[2*k].r-T[2*k].l+1);
    T[2*k+1].w+=T[k].lazy*(T[2*k+1].r-T[2*k+1].l+1);
    T[2*k].w%=mod;
    T[2*k+1].w%=mod;
    T[2*k].lazy%=mod;
    T[2*k+1].lazy%=mod;
    T[k].lazy=0;
}
void update(int k,int l,int r,int w)
{
    if(l<=T[k].l&&T[k].r<=r)
    {
        T[k].lazy+=w;
        T[k].w+=w*(T[k].r-T[k].l+1);
        T[k].lazy%=mod;
        T[k].w%=mod;
        return ;
    }
    if(T[k].lazy) down(k);
    int mid=(T[k].l+T[k].r)>>1;
    if(l<=mid) update(2*k,l,r,w);
    if(r>mid) update(2*k+1,l,r,w);
    T[k].w=T[2*k].w+T[2*k+1].w;
    T[k].w%=mod;
}
int query(int k,int l,int r)
{
    int ans=0;
    if(l<=T[k].l&&T[k].r<=r)
    {
        ans+=T[k].w;
        return ans;
    }
    if(T[k].lazy) down(k);
    int mid=(T[k].l+T[k].r)>>1;
    if(l<=mid) ans+=query(2*k,l,r);
    if(r>mid) ans+=query(2*k+1,l,r);
    return ans%mod;
}
/********树剖操作********/
void addxy(int x,int y,int w)
{
    while(top[x]!=top[y])//当两点不在同一条链上时
    {
        if(deep[top[x]]<deep[top[y]]) swap(x,y);//把x点改为所在链顶端的深度更深的那个点
        update(1,mark[top[x]],mark[x],w);//更新x点到x所在链顶端 这一段区间的点权和
        x=f[top[x]];//x跳到x所在链顶端的上一个点
    }
    //直到两点在同一链上时
    if(deep[x]>deep[y]) swap(x,y);//把x点深度更深的那个点
    update(1,mark[x],mark[y],w);//跟新两点区间的权
}
int sumxy(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])//同上
    {
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=query(1,mark[top[x]],mark[x]);
        ans%=mod;
        x=f[top[x]];
    }
    if(deep[x]>deep[y]) swap(x,y);
    ans+=query(1,mark[x],mark[y]);
    return ans%mod;
}
void addrt(int rt,int w)
{
    update(1,mark[rt],mark[rt]+se[rt]-1,w);//回忆dfs顺序,可知节点子节点编号为mark[rt]~mark[rt]+se[rt]-1
}
int sumrt(int rt)
{
    return query(1,mark[rt],mark[rt]+se[rt]-1);
}
int main()
{
    int opt,x,y,z;
    scanf("%d%d%d%d",&n,&m,&r,&mod);
    for(int i=1; i<=n; i++) scanf("%d",&A[i]);
    for(int i=1; i<n; i++)
    {
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    dfs1(r,-1,1);
    dfs2(r,r);
    build(1,1,n);
    while(m--)
    {
        scanf("%d",&opt);
        if(opt==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            addxy(x,y,z);
        }
        if(opt==2)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",sumxy(x,y));
        }
        if(opt==3)
        {
            scanf("%d%d",&x,&z);
            addrt(x,z);
        }
        if(opt==4)
        {
            scanf("%d",&x);
            printf("%d\n",sumrt(x));
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值