这显然是一个很高级(???)的算法,关于原理等东东这里将不会赘述;
这里主要是给出一些代码细节(这个模板写了我1.5h,中间犯了几个低级错误)
我的代码如下(自认为码风优良QwQ):
#include<stdio.h>
#include<iostream>
#define maxn 100010
#define maxm 200010
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define int long long
//骚操作QAQ,但似乎没必要
using namespace std;
int bg[maxn],nt[maxm],to[maxm],e;//链式前向星
int dep[maxn],f[maxn],siz[maxn],son[maxn];
int id[maxn],cnt,top[maxn],w[maxn];//树剖的dfs1,dfs2所需变量
int s[maxn<<2],p[maxn<<2];//线段树
int n,m,rt,mod,a[maxn];
void insert(int x,int y) {
nt[++e]=bg[x];
to[e]=y;
bg[x]=e;
}
void dfs1(int x,int fa,int deep) {
int i,u,mx=-1;
dep[x]=deep;
f[x]=fa;
siz[x]=1;
for (i=bg[x];i;i=nt[i]) {
u=to[i];
if (u==fa) continue;//千万别写成return了QAQ
dfs1(u,x,deep+1);
siz[x]+=siz[u];//回溯统计
if (siz[u]>mx) {
mx=siz[u];
son[x]=u;
}
}
}//dfs1:预处理各节点深度dep[x],父亲f[x],子树大小(包含自身)siz[x],重儿子son[x];
void dfs2(int x,int topfa) {//topfa记录当前重链的起始点(链上深度最小的)
int i,u;
id[x]=++cnt;
w[cnt]=a[x]%mod;
top[x]=topfa;
if (!son[x]) return ;//到了叶子
dfs2(son[x],topfa);//先拓展重儿子
for (i=bg[x];i;i=nt[i]) {
u=to[i];
if (u==f[x] || u==son[x]) continue;//拓展不是父亲也不是重儿子的子节点
dfs2(u,u);
}
}//dfs2:预处理各节点新编号id[x],新编号所对应点权w[x_new],以及该节点所在重链的起始点;
//以下线段树部分:
void push_down(int rt,int l,int r) {
if (p[rt]) {
p[rt<<1]=(p[rt<<1]+p[rt])%mod;
p[rt<<1|1]=(p[rt<<1|1]+p[rt])%mod;
s[rt<<1]=(s[rt<<1]+p[rt]*(mid-l+1))%mod;
s[rt<<1|1]=(s[rt<<1|1]+p[rt]*(r-mid))%mod;
p[rt]=0;
}
}
void build(int rt,int l,int r) {
if (l==r) {
s[rt]=w[l];
return ;
}
build(lson);
build(rson);
s[rt]=(s[rt<<1]+s[rt<<1|1])%mod;
}
void update(int rt,int l,int r,int L,int R,int pos) {
if (L<=l && r<=R) {
p[rt]=(p[rt]+pos)%mod;
s[rt]=(s[rt]+pos*(r-l+1))%mod;
return ;
}
push_down(rt,l,r);
if (L<=mid) update(lson,L,R,pos);
if (R>mid) update(rson,L,R,pos);
s[rt]=(s[rt<<1]+s[rt<<1|1])%mod;
}
int query(int rt,int l,int r,int L,int R) {
if (L<=l && r<=R) {
return s[rt];
}
push_down(rt,l,r);
int res=0;
if (L<=mid) res=query(lson,L,R)%mod;
if (R>mid) res=(res+query(rson,L,R))%mod;
return res%mod;
}
//以上是对于新编号的点用线段树维护(四步走)
void updRange(int x,int y,int z) {
while (top[x]!=top[y]) {
if (dep[top[x]]<dep[top[y]]) swap(x,y);
update(1,1,n,id[top[x]],id[x],z);//注意顺序:深度小的新编号id也会小,所以L=id[top[x]],R=id[x];
x=f[top[x]];
}//x,y不停地沿各自所在重链往上跑到重链起始点的父亲,同时更新沿途(当前点到重链顶端)的点权(注:此时的点权指的是w[id[x]],即新编号的点的点权),直到x,y跑到同一条重链上;
if (dep[x]<dep[y]) swap(x,y);
update(1,1,n,id[y],id[x],z);//同样注意顺序
}
void updSon(int x,int y) {
update(1,1,n,id[x],id[x]+siz[x]-1,y);//对于任一节点的子树,因为之前是dfs做预处理,故其所有新编号具有连续性(即若当前子树的根新编号为id[x],那么其子树中最大新编号为id[x]+siz[x]-1,准确的说,这个新编号最大的节点就是这课子树中最后被dfs到的(显然是叶子)节点)
}
int qRange(int x,int y) {
int ans=0;
while (top[x]!=top[y]) {
if (dep[top[x]]<dep[top[y]]) swap(x,y);
ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
x=f[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
ans=(ans+query(1,1,n,id[y],id[x]))%mod;
return ans;
}
int qSon(int x) {
return query(1,1,n,id[x],id[x]+siz[x]-1);
}
signed main(){
int i,op,x,y,z;
scanf("%lld%lld%lld%lld",&n,&m,&rt,&mod);
for (i=1;i<=n;i++) scanf("%lld",&a[i]);
for (i=1;i<n;i++) {
scanf("%lld%lld",&x,&y);
insert(x,y);
insert(y,x);
}
dfs1(rt,0,1);
dfs2(rt,rt);//两参是两个rt,别写成rt,0了QAQ
build(1,1,n);
for (i=1;i<=m;i++) {
scanf("%lld",&op);
if (op==1) {
scanf("%lld%lld%lld",&x,&y,&z);
updRange(x,y,z%mod);
}
else if (op==2) {
scanf("%lld%lld",&x,&y);
printf("%lld\n",qRange(x,y));
}
else if (op==3) {
scanf("%lld%lld",&x,&y);
updSon(x,y%mod);
}
else {
scanf("%lld",&x);
printf("%lld\n",qSon(x));
}
}
return 0;
}
很显然存在不少第一篇题解的痕迹 (Orz写题解的dalao),毕竟我就是看着它学会的
PS:为了谨慎,全开了long long,其实没必要。
又PS:单行注释过长带来的阅读不便,请见谅(打字不易),可以复制到自己电脑上看。
供大家参考!