动态点分治浅谈
动态点分治浅谈
一、前置知识
在学习动态点分治之前要会点分治,或者会点分治的思想,这里有我对点分治讲解:链接。其次,学习动态点分治还需要会一些单步容斥的思想。
二、浅谈
我们考虑一个用点分治能做的题目的特性:这个题目不能修改。那么对于要进行修改的树上问题,我们可以考虑动态点分治。
什么是动态点分治?动态点分治就是运用数据结构维护点分树上的信息。这里提到了点分树这个概念。点分树就是我们把用点分治求出的每一层重心之间连边之后得到的树。因为这棵树中点与点之间都是相邻层的重心,所以显然这棵树只有$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); } }