動態點分治淺談
一、前置知識
在學習動態點分治之前要會點分治,或者會點分治的思想,這里有我對點分治講解:鏈接。其次,學習動態點分治還需要會一些單步容斥的思想。
二、淺談
我們考慮一個用點分治能做的題目的特性:這個題目不能修改。那么對於要進行修改的樹上問題,我們可以考慮動態點分治。
什么是動態點分治?動態點分治就是運用數據結構維護點分樹上的信息。這里提到了點分樹這個概念。點分樹就是我們把用點分治求出的每一層重心之間連邊之后得到的樹。因為這棵樹中點與點之間都是相鄰層的重心,所以顯然這棵樹只有$log$層,因為這個性質十分優秀,我們可以每一次在點分樹上暴力向上跳統計答案。
考慮怎么統計答案,我們先來看一道例題:鏈接。
題目描述:在一片土地上有N個城市,通過N-1條無向邊互相連接,形成一棵樹的結構,相鄰兩個城市的距離為1,其中第i個城市的價值為value[i]。不幸的是,這片土地常常發生地震,並且隨着時代的發展,城市的價值也往往會發生變動。接下來你需要在線處理M次操作:
0 x k 表示發生了一次地震,震中城市為x,影響范圍為k,所有與x距離不超過k的城市都將受到影響,該次地震造成的經濟損失為所有受影響城市的價值和。
1 x y 表示第x個城市的價值變成了y。
為了體現程序的在線性,操作中的x、y、k都需要異或你程序上一次的輸出來解密,如果之前沒有輸出,則默認上一次的輸出為0。
我們對於當前的樹構造點分樹,這個一部分的代碼十分簡單,就是點分治的代碼。
inline void add(int a,int b) {nxt[++idx]=head[a],to[idx]=b,head[a]=idx;} inline void dfs(int p,int from) { dep[p]=dep[from]+1,pos[p]=++tot,f[0][tot]=dep[p]; for(register int i=head[p];i;i=nxt[i]) if(to[i]!=from) dfs(to[i],p),f[0][++tot]=dep[p]; } inline int getdis(int x,int y) { int tmp=dep[x]+dep[y];x=pos[x],y=pos[y]; if(x>y) swap(x,y); int k=lg[y-x+1]; return tmp-(min(f[k][x],f[k][y-(1<<k)+1])<<1); } inline void getroot(int p,int from) { mx[p]=0,size[p]=1; for(register int i=head[p];i;i=nxt[i]) if(to[i]!=from&&(!vis[to[i]])) { getroot(to[i],p),size[p]+=size[to[i]]; mx[p]=max(mx[p],size[to[i]]); } mx[p]=max(mx[p],all-size[p]); if(mx[p]<mx[root]) root=p; } inline void dfs2(int p,int from) { vis[p]=true; for(register int i=head[p];i;i=nxt[i]) if(!vis[to[i]]) { root=0,all=size[to[i]],getroot(to[i],0); fa[root]=p,dfs2(root,0); } } inline void init() { for(register int i=2;i<=tot;i++) lg[i]=lg[i>>1]+1; for(register int i=1;(1<<i)<=n;i++) for(register int j=1;j<=tot-(1<<i)+1;j++) f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]); root=0,mx[0]=n+1,all=n;getroot(1,0),dfs2(root,0); } int main() { n=read(),m=read(); for(register int i=1;i<=n;i++) num1[i]=read(); for(register int i=1,a,b;i<n;i++) a=read(),b=read(),add(a,b),add(b,a); dfs(1,0),init() }
這一部分的代碼和點分治的區別就是在$dfs2$中有一個點分治的賦值父親操作。因為后面我們要用到距離,所以在代碼中有一個初始化$lca$倍增數組的部分。
我們考慮怎么求距離和,我們在點分樹上的每一個點都架上一個線段樹,這棵線段樹的每一個節點是,在點分樹上以當前節點為根的子樹中的點,和當前節點的距離在$[l,r]$范圍內的點權和。這樣我們就可以直接在線段樹上找,在以當前節點為根的子樹中,和當前節點距離小於等於$x$的點權和是多少。但是我們發現這樣會有問題,我們會算重復,因為一個點可能會被當前點以及當前點的祖先計算多次。這樣我們考慮單步容斥。
我們在點分樹上的每一個節點再架一棵線段樹,這個線段樹的每一個節點是,在點分樹上以當前節點為根的子樹中的點,和當前節點在點分樹上父親的的距離在$[l,r]$范圍內的點權和。這樣我們每一次只需要容斥一下就可以了,可能我所描述的不太好理解,觀察下面代碼就好了。
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define N 100010 #define min(i,j) ((i<j)?i:j) #define max(i,j) ((i>j)?i:j) #define O2 __attribute__((optimize("-O2"))) inline char nc() { static char buf[1000000],*p1,*p2; return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++; } inline int read() { int re=0; char f=0,ch=nc(); while(!isdigit(ch)) {f|=(ch=='-'); ch=nc();} while(isdigit(ch)) re=re*10+(ch^'0'),ch=nc(); return f ? -re : re; } int n,m,f[18][N<<1],pos[N],mx[N],level[N],dis[N],size[N],num[N],num1[N],ans,lg[N<<1],cnt,tot; int root,all,dep[N],head[N],to[N<<1],nxt[N<<1],idx,times;bool vis[N]; int root1[N],root2[N],fa[N],son[N*35][2],sum[N*35]; inline void add(int a,int b) {nxt[++idx]=head[a],to[idx]=b,head[a]=idx;} inline void dfs(int p,int from) { dep[p]=dep[from]+1,pos[p]=++tot,f[0][tot]=dep[p]; for(register int i=head[p];i;i=nxt[i]) if(to[i]!=from) dfs(to[i],p),f[0][++tot]=dep[p]; } inline int getdis(int x,int y) { int tmp=dep[x]+dep[y];x=pos[x],y=pos[y]; if(x>y) swap(x,y); int k=lg[y-x+1]; return tmp-(min(f[k][x],f[k][y-(1<<k)+1])<<1); } inline void getroot(int p,int from) { mx[p]=0,size[p]=1; for(register int i=head[p];i;i=nxt[i]) if(to[i]!=from&&(!vis[to[i]])) getroot(to[i],p), size[p]+=size[to[i]],mx[p]=max(mx[p],size[to[i]]); mx[p]=max(mx[p],all-size[p]); if(mx[p]<mx[root]) root=p; } inline void getsize(int p,int from,int anc) { /*size[p]=1,*/level[p]=level[from]+1,dis[anc]=max(dis[anc],level[p]); for(register int i=head[p];i;i=nxt[i]) if(to[i]!=from&&(!vis[to[i]])) getsize(to[i],p,anc)/*,size[p]+=size[to[i]]*/; } inline void dfs2(int p,int from) { vis[p]=true,getsize(p,from,p); for(register int i=head[p];i;i=nxt[i]) if(!vis[to[i]]) root=0,all=size[to[i]],getroot(to[i],0),fa[root]=p,dfs2(root,0); } inline void init() { for(register int i=2;i<=tot;i++) lg[i]=lg[i>>1]+1; for(register int i=1;(1<<i)<=n;i++) for(register int j=1;j<=tot-(1<<i)+1;j++) f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]); root=0,mx[0]=n+1,all=n;getroot(1,0),dfs2(root,0); } inline int find(int p,int l,int r,int x,int y) { if(!p) return 0; if(x<=l&&r<=y) return sum[p]; register int mid=(l+r)>>1,tmp=0; if(x<=mid) tmp+=find(son[p][0],l,mid,x,y); if(y>mid) tmp+=find(son[p][1],mid+1,r,x,y); return tmp; } inline void find(int x,int y) { for(register int i=x;i;i=fa[i]) { if(getdis(x,i)<=y) ans+=find(root1[i],0,dis[i]-1,0,y-getdis(x,i)); if(fa[i]&&getdis(x,fa[i])<=y) ans-=find(root2[i],0,dis[fa[i]]-1,0,y-getdis(x,fa[i])); } } inline void change(int &p,int l,int r,int x,int y) { if(!p) p=++cnt; sum[p]+=y; if(l==r) return; register int mid=(l+r)>>1; if(x<=mid) change(son[p][0],l,mid,x,y); else change(son[p][1],mid+1,r,x,y); } inline void change(int x,int y) { for(register int i=x;i;i=fa[i]) { change(root1[i],0,dis[i]-1,getdis(i,x),y-num[x]); if(fa[i]) change(root2[i],0,dis[fa[i]]-1,getdis(x,fa[i]),y-num[x]); } num[x]=y; } inline void lots_of_changes() {for(register int i=1;i<=n;i++) change(i,num1[i]);} int main() { n=read(),m=read(); for(register int i=1;i<=n;i++) num1[i]=read(); for(register int i=1,a,b;i<n;i++) a=read(),b=read(),add(a,b),add(b,a); dfs(1,0),init(),lots_of_changes(); for(register int i=1,kind,a,b;i<=m;i++) { kind=read(),a=read()^ans,b=read()^ans; if(kind==0) ans=0,find(a,b),printf("%d\n",ans); if(kind==1) change(a,b); } }