并不对劲的片手流在为很对劲的太刀流调树剖时发现线段树写错了的时候整个人都不好了,决定反驳隔壁很对劲的太刀流并与之针锋相对。
听上去像是熟练剖粪。
一棵树可以看成是很多条链组成的。那么把这些链拼成一条线,在树上进行区间操作时就可以将每次操作的部分拆成很多段连续的部分。这样就可以用线段树什么的维护了。那么问题又来了,如果瞎拆的话,可能会有一次操作涉及的链很倒霉,每一个点都被断成了一部分。这样就不得不用链的长度的时间复杂度来执行此次操作。能不能让每条路径被断的次数小一些呢?想必是能的,这要用到轻重链剖分。
重链上的点在线段树上是一段连续是空间,所以要使每条路径上的点少一些。大致想一个贪心策略,可以尽量往点多的地方分重链。这样的话,从点u出发被分到轻链的子树大小肯定不超过u的子树大小的一般。每次走轻链都会使得子树大小变为原来的一半或更小,路径上的轻链就是log n级别的了。
这样照着子树大小将树分为重链和轻链,从根往下走,每次先走重链,按照dfs序列就可以将树拉成一条链了。注意记录一下对于每个点x,top(x)表示x所在重链的深度最浅的点。
该如何区间操作呢?会发现,对一条路径操作时,可以将路径分为很多条重链。
对于如图所示的一棵树,假设要对15到14的路径操作,可以先操作14,15到各自的top这一段,也就是15到20,14到8。然后找到20,8各自的父亲。这时两个点在同一条重链上,直接修改它们之间的部分就行了。也就是说,对于两个点x,y,要想对它们之间的路径进行操作,就可以操作它们到top(x),top(y)的一段。然后再走到top(x),top(y)的父亲,再重复这个过程。需要注意的是,每次应该走top最深的点,以防两个点错过了。
令人高兴的是,某点的子树在dfs序列中肯定是连续的一段,所以可以直接进行子树操作。
令人头疼的是,这样一来,线段树上支持的所有区间操作都可以在树上做了。
#include<iostream> #include<iomanip> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define maxn 100010 #define LL long long #define re register #define m ((l+r)>>1) #define ls (node<<1) #define rs (node<<1|1) #define vv v[k] using namespace std; LL n,q,r,p; LL fir[maxn],nxt[maxn*2],v[maxn*2],s[maxn],cnt; LL siz[maxn],fa[maxn],dep[maxn],top[maxn],son[maxn],mp[maxn],tim=0; inline LL read() { LL xx=0,ff=1; char ch=getchar(); while(isdigit(ch)==0 && ch!='-')ch=getchar(); if(ch=='-')ff=-1,ch=getchar(); while(isdigit(ch))xx=xx*10+ch-'0',ch=getchar(); return xx*ff; } inline void write(LL x) { int ff=0;char ch[15]; if(x<0) { x=-x; putchar('-'); } while(x)ch[++ff]=(x%10)+'0',x/=10; if(ff==0)putchar('0'); while(ff)putchar(ch[ff--]); putchar('\n'); }struct SegmentTree { LL tre[maxn<<2],mark[maxn<<2]; LL a[maxn],yes[maxn<<2]; void pushup(LL node) { tre[node]=(tre[ls]+tre[rs])%p; } void pushdown(LL node,LL l,LL r) { if(l<r && mark[node]) { tre[ls]+=mark[node]*(m-l+1); tre[rs]+=mark[node]*(r-m); mark[ls]+=mark[node]; mark[rs]+=mark[node]; mark[node]=0; tre[ls]%=p;tre[rs]%=p;mark[ls]%=p;mark[rs]%=p; } } void build(LL node,LL l,LL r) { if(l==r) { tre[node]=a[l]; return; } build(ls,l,m); build(rs,m+1,r); pushup(node); } LL ask(LL node,LL l,LL r,LL x,LL y) { pushdown(node,l,r); if(x<=l && r<=y) return tre[node]%p; if(y<l || r<x) return 0; LL lans=ask(ls,l,m,x,y),rans=ask(rs,m+1,r,x,y); return (lans+rans)%p; } void add(LL node,LL l,LL r,LL x,LL y,LL k) { pushdown(node,l,r); if(x<=l && r<=y) { mark[node]+=k; tre[node]+=k*(r-l+1); tre[node]%=p,mark[node]%=p; return; } if(y<l || r<x) return; add(ls,l,m,x,y,k); add(rs,m+1,r,x,y,k); pushup(node); } }t; inline void getson(LL u) { siz[u]=1;LL maxson=0; for(re LL k=fir[u];k!=-1;k=nxt[k]) { if(vv!=fa[u]) { fa[vv]=u,dep[vv]=dep[u]+1; getson(vv); maxson= siz[maxson]<siz[vv] ? vv : maxson; siz[u]+=siz[vv]; } } son[u]=maxson; } inline void gettop(LL u,LL anc) { mp[u]=++tim, t.a[tim]=s[u],top[u]=anc; if(son[u]!=0)gettop(son[u],anc); for(re LL k=fir[u];k!=-1;k=nxt[k]) { if(vv!=fa[u] && vv!=son[u]) gettop(vv,vv); } } inline void get_num() { LL x=read(),y=read(),tx=top[x],ty=top[y],ans=0; while(tx!=ty) { if(dep[ty]>dep[tx])swap(x,y),swap(tx,ty); ans+= t.ask(1,1,n,mp[tx],mp[x]); x=fa[tx],tx=top[x]; } ans+=t.ask(1,1,n,min(mp[x],mp[y]),max(mp[x],mp[y])); write(ans%p); } inline void add_num() { LL x=read(),y=read(),ad=read(),tx=top[x],ty=top[y],ans=0; while(tx!=ty) { if(dep[ty]>dep[tx])swap(x,y),swap(tx,ty); t.add(1,1,n,mp[tx],mp[x],ad); x=fa[tx],tx=top[x]; } t.add(1,1,n,min(mp[x],mp[y]),max(mp[x],mp[y]),ad); } inline void get_kid_num() { LL x=read(),ans=t.ask(1,1,n,mp[x],mp[x]+siz[x]-1); write(ans%p); } inline void add_kid_num() { LL x=read(),ad=read(); t.add(1,1,n,mp[x],mp[x]+siz[x]-1,ad); } int main() { n=read(),q=read(),r=read(),p=read(); for(re LL i=1;i<=n;i++) { s[i]=read(); } memset(fir,-1,sizeof(fir)); for(re LL i=1;i<n;i++) { LL u=read(); v[++cnt]=read(); nxt[cnt]=fir[u]; fir[u]=cnt; v[++cnt]=u,u=v[cnt-1]; nxt[cnt]=fir[u]; fir[u]=cnt; } dep[r]=1; getson(r); gettop(r,r); t.build(1,1,n); for(re int i=1;i<=q;i++) { LL f=read(); if(f==1)add_num(); if(f==2)get_num(); if(f==3)add_kid_num(); if(f==4)get_kid_num(); } return 0; }