参考博客:https://www.cnblogs.com/ivanovcraft/p/9019090.html
#include<iostream> #include<cstdio> #define int long long using namespace std; const int maxn=1e5+10; struct edge { int next,to; } e[maxn*2]; int head[maxn],cnt; struct node { int l,r,ls,rs,sum,lazy; } a[maxn*2]; int n,m,r,rt,mod; int v[maxn]; int f[maxn];//f[u] 保存结点u的父亲节点 int d[maxn];//d[u] 保存结点u的深度值 int son[maxn];//son[u] 保存重儿子 int size[maxn];//size[u] 保存以u为根的子树节点个数 int top[maxn];//top[u] 保存当前节点所在链的顶端节点 int id[maxn];//id[u] 保存树中每个节点剖分以后的新编号(DFS的执行顺序) int rk[maxn];//rk[u] 保存当前dfs标号在树中所对应的节点 void add(int x,int y) { e[++cnt].next=head[x]; e[cnt].to=y; head[x]=cnt; } void dfs1(int x) //当前节点 { size[x]=1; d[x]=d[f[x]]+1; for(int v,i=head[x]; i; i=e[i].next) if((v=e[i].to)!=f[x]) { f[v]=x; dfs1(v); size[x]+=size[v];//子节点的size已被处理,用它来更新父节点的size if(size[son[x]]<size[v]) son[x]=v; //选取size最大的作为重儿子 } } void dfs2(int x,int tp)//当前节点、重链顶端 { top[x]=tp; id[x]=++cnt;//标记dfs序 rk[cnt]=x;//序号cnt对应节点u if(son[x]) dfs2(son[x],tp); /*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续, 一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/ for(int v,i=head[x]; i; i=e[i].next) if((v=e[i].to)!=f[x]&&v!=son[x]) dfs2(v,v);//一个点位于轻链底端,那么它的top必然是它本身 } inline void pushup(int x) { a[x].sum=(a[a[x].ls].sum+a[a[x].rs].sum)%mod; } void build(int l,int r,int x) { if(l==r) { a[x].sum=v[rk[l]],a[x].l=a[x].r=l; return; } int mid=l+r>>1; a[x].ls=cnt++,a[x].rs=cnt++; build(l,mid,a[x].ls),build(mid+1,r,a[x].rs); a[x].l=a[a[x].ls].l,a[x].r=a[a[x].rs].r; pushup(x); } inline int len(int x) { return a[x].r-a[x].l+1; } inline void pushdown(int x) { if(a[x].lazy) { int ls=a[x].ls,rs=a[x].rs,lz=a[x].lazy; (a[ls].lazy+=lz)%=mod,(a[rs].lazy+=lz)%=mod; (a[ls].sum+=lz*len(ls))%=mod,(a[rs].sum+=lz*len(rs))%=mod; a[x].lazy=0; } } void update(int l,int r,int c,int x) { if(a[x].l>=l&&a[x].r<=r) { (a[x].lazy+=c)%=mod,(a[x].sum+=len(x)*c)%=mod; return; } pushdown(x); int mid=a[x].l+a[x].r>>1; if(mid>=l) update(l,r,c,a[x].ls); if(mid<r) update(l,r,c,a[x].rs); pushup(x); } int query(int l,int r,int x) { if(a[x].l>=l&&a[x].r<=r) return a[x].sum; pushdown(x); int mid=a[x].l+a[x].r>>1,tot=0; if(mid>=l) tot+=query(l,r,a[x].ls); if(mid<r) tot+=query(l,r,a[x].rs); return tot%mod; } inline int sum(int x,int y) { int ret=0; while(top[x]!=top[y])//两点不在同一条重链 { if(d[top[x]]<d[top[y]]) swap(x,y); (ret+=query(id[top[x]],id[x],rt))%=mod;//线段树区间求和,处理这条重链的贡献 x=f[top[x]];//将x设置成原链头的父亲结点,走轻边,继续循环 } //循环结束,两点位于同一重链上,但两点不一定为同一点,所以我们还要统计这两点之间的贡献 if(id[x]>id[y]) swap(x,y); return (ret+query(id[x],id[y],rt))%mod; } inline void updates(int x,int y,int c) { while(top[x]!=top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); update(id[top[x]],id[x],c,rt); x=f[top[x]]; } if(id[x]>id[y]) swap(x,y); update(id[x],id[y],c,rt); } signed main() { scanf("%lld%lld%lld%lld",&n,&m,&r,&mod); for(int i=1; i<=n; i++) scanf("%lld",&v[i]); for(int x,y,i=1; i<n; i++) { scanf("%lld%lld",&x,&y); add(x,y),add(y,x); } cnt=0,dfs1(r),dfs2(r,r); cnt=0,build(1,n,rt=cnt++); for(int op,x,y,k,i=1; i<=m; i++) { scanf("%lld",&op); if(op==1)//将树从 xx 到 yy 结点最短路径上所有节点的值都加上 zz { scanf("%lld%lld%lld",&x,&y,&k); updates(x,y,k); } else if(op==2)//求树从 xx 到 yy 结点最短路径上所有节点的值之和。 { scanf("%lld%lld",&x,&y); printf("%lld ",sum(x,y)); } else if(op==3)//将以 xx 为根节点的子树内所有节点值都加上 zz { scanf("%lld%lld",&x,&y); update(id[x],id[x]+size[x]-1,y,rt); } else//以 xx 为根节点的子树内所有节点值之和 { scanf("%lld",&x); printf("%lld ",query(id[x],id[x]+size[x]-1,rt)); } } return 0; }