题目描述
如题,已知一棵包含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;
}