【模板】树链剖分

题目描述

如题,已知一棵包含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

输出格式:

输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模)

输入输出样例

输入样例#1:

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

输出样例#1:

2
21

说明


对于100%的数据: N≤10^​5,M≤10^5
​​

思路

裸的树链剖分(喂这不本来就是模板题吗

树链剖分其实就是对于一棵树

先进行两遍DFS

第一遍找到他位于树中的深度,他的父节点,包括他在内下方节点的数量

找到数量最多的设为重儿子,有多个相同的任意选一个

将重儿子连成一条重链,其他儿子以自己为初始节点建一条重链

重新编号,编号顺序是第一条重链,其他儿子的重链

以新编号建立线段树,进行对区间的更新和求和(也就是1和2是线段树的基本操作)

3和4对于在同一个重链的节点很好找

对于不在同一重链的先找到更深的,找到它所在重链的链头

再找链头的父亲,再找深度,重复操作

最终是可以到同一条重链的

话说代码是真的长,注释是真的多

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const long long MAXN=500010;//线段树至少4倍 
long long N,M,R,P;
//分别表示树的结点个数、操作个数、根节点序号和取模数
long long a[MAXN];//存点的权值 
long long h[MAXN],cnt; //存图 
long long fa[MAXN],dep[MAXN],size[MAXN];
//父节点,深 度,包括本身的下方的点的数量 
long long hson[MAXN],top[MAXN];
//重儿子,重链顶点
long long id[MAXN],real[MAXN],num;
//新编号和原编号 序号 
struct Tu{
    long long to,next;
}e[MAXN];//图 
struct Tr{
    long long l,r,flag,sum; 
}tree[MAXN];//树 
inline long long read()
{
    long long sum=0; 
    char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch<='9'&&ch>='0') sum=sum*10+ch-48,ch=getchar();
    return sum;
}//读入优化 
void add(long long x,long long y)
{
    e[++cnt].to=y;
    e[cnt].next=h[x];
    h[x]=cnt;
}//建图 
void DFS1(long long u,long long f) //u为该节点,f为父节点 
{
    size[u]=1;//目前链长为一 
    fa[u]=f,dep[u]=dep[f]+1;//更新父节点和深度 
    for(int i=h[u];i;i=e[i].next)
    {
        long long v=e[i].to;
        if(v!=f)//如果不是父节点
        {
            DFS1(v,u);//继续深搜
            size[u]+=size[v];//递归回来更新链长 
            if(hson[u]==0||size[hson[u]]<size[v]) hson[u]=v;
            //更新重儿子,没有重儿子就随意选 
        }
    } 
}//建树
void DFS2(long long u,long long first) 
{
    top[u]=first;//找到开端 
    id[u]=++num,real[num]=u;//id为重编号,real存原编号 
    if(hson[u]==0) return;//叶子即边界 
    DFS2(hson[u],first);//有重儿子继续找 
    for(int i=h[u];i;i=e[i].next)
    {
        long long v=e[i].to; 
        if(v!=fa[u]&&v!=hson[u]) DFS2(v,v);
        //其他不是重儿子的子节点单独开一条新链 
    } 
}//找重链的顶点,重编号
void PushUp(long long root)
{
    tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
}//上推操作
void PushDown(long long root)
{
    if(tree[root].flag>0)
    {
        tree[root<<1].flag+=tree[root].flag;
        tree[root<<1|1].flag+=tree[root].flag;
        tree[root<<1].sum+=tree[root].flag*(tree[root<<1].r-tree[root<<1].l+1);
        tree[root<<1|1].sum+=tree[root].flag*(tree[root<<1|1].r-tree[root<<1|1].l+1);
        tree[root].flag=0;
    }
}//懒操作 
void Build(long long root,long long l,long long r)
{
    tree[root].l=l;tree[root].r=r;
    if(l==r)
    {
        tree[root].sum=a[real[l]];
        return;
    }//设置边界条件
    long long mid=(l+r)>>1;
    Build(root<<1,l,mid); 
    Build(root<<1|1,mid+1,r);
    PushUp(root);//上推 
}//建线段树
void Update(long long root,long long ll,long long rr,long long w)
{
    if(ll<=tree[root].l&&rr>=tree[root].r)
    {
        tree[root].flag+=w;
        tree[root].sum+=w*(tree[root].r-tree[root].l+1);
        return;
    }
    PushDown(root);
    long long mid=(tree[root].l+tree[root].r)>>1;
    if(rr<=mid) Update(root<<1,ll,rr,w);
    else if(ll>mid) Update(root<<1|1,ll,rr,w);
    else
    {
        Update(root<<1,ll,mid,w);
        Update(root<<1|1,mid+1,rr,w);   
    }
    PushUp(root);
}//区间修改 
long long Query_sum(long long root,long long ll,long long rr)
{
    if(ll<=tree[root].l&&rr>=tree[root].r)
     return tree[root].sum;
    PushDown(root);
    long long mid=(tree[root].l+tree[root].r)>>1;
    if(rr<=mid)return Query_sum(root<<1,ll,rr);
    if(ll>mid) return Query_sum(root<<1|1,ll,rr);
    return Query_sum(root<<1,ll,mid)+Query_sum(root<<1|1,mid+1,rr);
}//区间求和 
void change(long long u,long long v,long long x)
{
    long long tu=top[u],tv=top[v];//找到链头 
    while(tu!=tv)
    {
        if(dep[tu]<dep[tv])
         swap(tu,tv),swap(u,v);//保持u比v深 
        Update(1,id[tu],id[u],x);
        u=fa[tu],tu=top[u];
    }
    if(dep[u]>dep[v]) swap(u,v);
    Update(1,id[u],id[v],x);//更新从u比v到值所以u要比v浅 
}//区间修改 
long long find_sum(long long u,long long v)
{
    long long tu=top[u],tv=top[v],sum=0;
    while(tu!=tv)
    {
        if(dep[tu]<dep[tv])
         swap(tu,tv),swap(u,v);
        sum+=Query_sum(1,id[tu],id[u]);sum=sum%P;
        u=fa[tu],tu=top[u];
    }
    if(dep[u]>dep[v]) swap(u,v);
    sum+=Query_sum(1,id[u],id[v]);
    return sum%P;
}//区间求和,大致同上 
void root_add(long long u,long long x)
{
    long long begin=id[u];
    long long end=id[u]+size[u]-1;
    Update(1,begin,end,x);
}//子树修改 
long long root_sum(long long u)
{
    long long begin=id[u];
    long long end=id[u]+size[u]-1;
    return Query_sum(1,begin,end)%P;
}//子树求和 
int main()
{
    //freopen("1.in","r",stdin);
    long long z,x,y,ask;
    N=read(),M=read(),R=read(),P=read();
    for(int i=1;i<=N;++i) a[i]=read(); //输入各个点的值 
    for(int i=1;i<N;++i) 
    {
        x=read(),y=read();
        add(x,y),add(y,x); 
    }//建图
    DFS1(R,0);//建树
    DFS2(R,R);//找重链的顶点,重编号
    Build(1,1,num);//以新编号建线段树 
    for(int i=1;i<=M;++i)
    {
        ask=read();
        if(ask==1) x=read(),y=read(),z=read(),change(x,y,z);            //区间修改 
        else if(ask==2) x=read(),y=read(),printf("%d\n",find_sum(x,y)); //区间求和 
        else if(ask==3) x=read(),z=read(),root_add(x,z);                //子树修改 
        else if(ask==4) x=read(),printf("%d\n",root_sum(x));            //子树求和 
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值