【模板】树链剖分

来源:互联网 发布:装配式钢结构 知乎 编辑:程序博客网 时间:2024/05/16 07:20

题目描述

如题,已知一棵包含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;}
原创粉丝点击