洛谷 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));
}
}
}