对一棵树进行剖分,将其分红几条链,将树形变为线性,减小处理的难度
须要处理的问题有node
void dfs1(int x,int f,int deep)//x当前节点,f父亲,deep深度 { dep[x]=deep;//标记深度 fa[x]=f;//标记每一个点的父亲 siz[x]=1;//标记每一个非叶子节点的子树的大小 int maxson=-1;//记录重儿子的儿子数量 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==f) continue;//若是是父亲那么就继续去找下一个 dfs1(y,x,deep+1); siz[x]+=siz[y];//加上子树的节点数量 if(siz[y]>maxson) { son[x]=y; maxson=siz[y];//若是该子节点更大,那么就标记他的每一个非叶子节点的 //重儿子的编号 } } }
void dfs2(int x,int topf)//x为当前的节点,topf为当前链上最顶端的节点 { id[x]=++cnt;//标记每一个点的新编号 wt[cnt]=w[x];//把每一个点的初始值都赋到新的编号上来 top[x]=topf;//标记这个点所在的链的顶端 if(!son[x]) return;//若是没有重儿子(儿子),那么就返回 dfs2(son[x],topf);//先处理重儿子,在处理轻儿子,递归处理 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==fa[x]||y==son[x])continue; //若是遍历到父亲结点或者是重儿子,那么就继续搜索 dfs2(y,y); //每个轻儿子都有一条从本身开始的链 } }
由于顺序是按照先重儿子,再轻儿子来处理的,因此每一条重链的新编号是连续的
由于是用的\(DFS\)因此每个子树的新编号也是连续的ios
在这个时候咱们注意到,咱们所要处理的全部的区间都是连续的编号(新编号),那么咱们能够用线段树处理连续编号区间和,每次查询时间复杂度为\(O(log^2n)\)数组
int qRange(int x,int y) { int ans=0; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//把x改在所在链更深的点 swap(x,y); res=0; query(1,1,n,id[top[x]],id[x]);//ans加上x点到所在链顶端的这一区间的点权和 ans+=res; ans%=mod; x=fa[top[x]]; } if(dep[x]>dep[y])swap(x,y); res=0; query(1,1,n,id[x],id[y]); ans+=res; return ans%mod; }
记录每个非叶子节点的子树的大小,而且每个子树的新编号都是连续的,因而就直接线段树区间查询便可时间复杂度为\(O(log n)\)ui
int qson(int x) { res=0; query(1,1,n,id[x],id[x]+siz[x]-1);//子树的右端点为id[x]+siz[x]-1,能够手推一下 return res; }
void updson(int x,int k) { update(1,1,n,id[x],id[x]+siz[x]-1,k); } void updRange(int x,int y,int k)//区间修改 { k%=mod; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//让x的深度更深 swap(x,y); update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k); }
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<map> #include<cmath> #include<algorithm> using namespace std; #define mid ((l+r)>>1) #define lson rt<<1,l,mid #define rson rt<<1|1,mid+1,r #define len (r-l+1) const int N=2e5+10; struct node{ int last; int to; }e[N]; int head[N]; int n,m,r,mod; int e_cnt,w[N],wt[N]; int a[N<<2],laz[N<<2]; //线段树数组,懒惰标记 int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N]; //重儿子编号,新编号,父亲结点,dfs序,深度,子树的大小,当前链的顶端结点 int res=0; void add(int from,int to) { e[++e_cnt].last=head[from]; e[e_cnt].to=to; head[from]=e_cnt; } //------------------------------------------------线段树 void pushdown(int rt,int lenn) { laz[rt<<1]+=laz[rt]; laz[rt<<1|1]+=laz[rt]; a[rt<<1]+=laz[rt]*(lenn-(lenn>>1)); a[rt<<1|1]+=laz[rt]*(lenn>>1); a[rt<<1]%=mod; a[rt<<1|1]%=mod; laz[rt]=0; } void build(int rt,int l,int r) { if(l==r) { a[rt]=wt[l];//赋值点权值 if(a[rt]>mod) a[rt]%=mod; return; } build(lson); build(rson); a[rt]=(a[rt<<1]+a[rt<<1|1])%mod; } void query(int rt,int l,int r,int L,int R) { if(L<=l&&r<=R) { res+=a[rt]; res%=mod; return; } else { if(laz[rt]) pushdown(rt,len); if(L<=mid) query(lson,L,R); if(R>mid) query(rson,L,R); } } void update(int rt,int l,int r,int L,int R,int k) //当前节点,当前区间的左,右,要修改的区间左,右,修改值 { if(L<=l&&r<=R) { laz[rt]+=k; a[rt]+=k*len; } else { if(laz[rt]) pushdown(rt,len); if(L<=mid) update(lson,L,R,k); if(R>mid) update(rson,L,R,k); a[rt]=(a[rt<<1]+a[rt<<1|1])%mod; } } //------------------------------------------------树链剖分 int qRange(int x,int y) { int ans=0; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//把x改在所在链更深的点 swap(x,y); res=0; query(1,1,n,id[top[x]],id[x]);//ans加上x点到所在链顶端的这一区间的点权和 ans+=res; ans%=mod; x=fa[top[x]]; } if(dep[x]>dep[y])swap(x,y); res=0; query(1,1,n,id[x],id[y]); ans+=res; return ans%mod; } void updRange(int x,int y,int k)//区间修改 { k%=mod; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]])//让x的深度更深 swap(x,y); update(1,1,n,id[top[x]],id[x],k); x=fa[top[x]]; } if(dep[x]>dep[y]) swap(x,y); update(1,1,n,id[x],id[y],k); } int qson(int x) { res=0; query(1,1,n,id[x],id[x]+siz[x]-1); return res; } void updson(int x,int k) { update(1,1,n,id[x],id[x]+siz[x]-1,k); } void dfs1(int x,int f,int deep)//x当前节点,f父亲,deep深度 { dep[x]=deep;//标记深度 fa[x]=f;//标记每一个点的父亲 siz[x]=1;//标记每一个非叶子节点的子树的大小 int maxson=-1;//记录重儿子的儿子数量 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==f) continue;//若是是父亲那么就继续去找下一个 dfs1(y,x,deep+1); siz[x]+=siz[y];//加上子树的节点数量 if(siz[y]>maxson) { son[x]=y; maxson=siz[y];//若是该子节点更大,那么就标记他的每一个非叶子节点的 //重儿子的编号 } } } void dfs2(int x,int topf)//x为当前的节点,topf为当前链上最顶端的节点 { id[x]=++cnt;//标记每一个点的新编号 wt[cnt]=w[x];//把每一个点的初始值都赋到新的编号上来 top[x]=topf;//标记这个点所在的链的顶端 if(!son[x]) return;//若是没有重儿子(儿子),那么就返回 dfs2(son[x],topf);//先处理重儿子,在处理轻儿子,递归处理 for(int i=head[x];i;i=e[i].last) { int y=e[i].to; if(y==fa[x]||y==son[x])continue; //若是遍历到父亲结点或者是重儿子,那么就继续搜索 dfs2(y,y); //每个轻儿子都有一条从本身开始的链 } } int main() { cin>>n>>m>>r>>mod; for(int i=1;i<=n;i++) cin>>w[i];//节点的初始权值 for(int i=1;i<n;i++) { int x,y; cin>>x>>y; add(x,y); add(y,x); } dfs1(r,0,1); dfs2(r,r); build(1,1,n); while(m--) { int k,x,y,z; cin>>k; if(k==1) { cin>>x>>y>>z; updRange(x,y,z); } else if(k==2) { cin>>x>>y; cout<<qRange(x,y)<<endl; } else if(k==3) { cin>>x>>y; updson(x,y); } else { cin>>x; cout<<qson(x)<<endl; } } return 0; }