动态点分治(学习笔记)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sizeof_you/article/details/85306426

机房最后一个学习动态点分治的人

前置知识:点分治(雾

动态点分治也叫点分树,就是把点分治时候每个重心组成的树拿出来用于动态维护一些东西

建点分树的过程就是点分治过程,同时记下每层重心的父亲(也就是上一层重心),一般是提前建出点分树,然后每次修改和询问时暴力跳树,因为点分树的树高是 l o g n logn 级别的,所以总复杂度是 n l o g n nlogn
(当然有时候需要一些数据结构可能会成为 n l o g 2 n nlog^2n

例题:
bzoj3730: 震波
同样的套路,先建出点分树并且记下每个点的 d e p dep (就是层数),和它上几层的父亲(它上面每一层的重心),还有它到这些父亲的距离。
用两个树状数组 f , g f,g 分别求离 x x 距离为 i i 的点的权值和,离 f a x fa_x 距离为 i i 的点的权值和,修改的时候就暴力跳 d e p dep ,修改 f , g f,g ,查询的时候也是,可以知道每个重心到它的距离,用 k k 减去就是重心到所求点的距离设为 x x ,用点分树的这个子树中到根距离不超过 x x 的权值加起来,因为会算重,所以还要减掉离儿子的父亲距离不超过 x x 的权值。(如果不明白可以画画图!

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
#define LL long long
using namespace std;

inline int rd(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}

int n,m,val[N],cnt,head[N],nxt[N<<1],to[N<<1];
int fa[N][20],dis[N][20],rt,mx,siz[N],dep[N],ans;
vector<int> f[N],g[N];
bool vis[N];

inline void add(int x,int y){
	to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
	to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}

void getroot(int u,int fat,int tot){
	siz[u]=1; int maxx=0;
	for(int i=head[u],v;i;i=nxt[i])
		if(!vis[v=to[i]] && v!=fat){
			getroot(v,u,tot);
			siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
		}
	maxx=max(maxx,tot-siz[u]);
	if(maxx<mx) mx=maxx,rt=u;
}

void getdis(int u,int fat,int t,int d){
	for(int i=head[u],v;i;i=nxt[i])
		if(!vis[v=to[i]] && v!=fat){
			fa[v][++dep[v]]=t,dis[v][dep[v]]=d;
			getdis(v,u,t,d+1);
		}
}

void Divide(int root,int tot){
	vis[root]=1; getdis(root,0,root,1);
	f[root].resize(tot+1); g[root].resize(tot+1);
	for(int i=head[root],v;i;i=nxt[i])
		if(!vis[v=to[i]]){
			int now=siz[v]>siz[root]?tot-siz[root]:siz[v];
			mx=n+1; getroot(v,root,now);
			Divide(rt,now);
		}
}

inline int query1(int x,int k){int sum=val[x],lim=f[x].size()-1; k=min(k,lim); for(;k;k-=k&-k) sum+=f[x][k];return sum;}
inline int query2(int x,int k){int sum=0,     lim=g[x].size()-1; k=min(k,lim); for(;k;k-=k&-k) sum+=g[x][k];return sum;}

inline void change(int x,int v){
	int lim=f[x].size()-1,d=dis[x][dep[x]];
	for(int i=d;i<=lim&&i;i+=i&-i) g[x][i]+=v;
	for(int i=dep[x];i;i--){
		d=dis[x][i]; lim=f[fa[x][i]].size()-1;
		for(int j=d;j<=lim;j+=j&-j) f[fa[x][i]][j]+=v;
		d=dis[x][i-1];
		for(int j=d;j<=lim&&j;j+=j&-j) g[fa[x][i]][j]+=v;
	}
}

inline int ask(int x,int k){
	int ret=query1(x,k);
	for(int i=dep[x];i;i--)
		if(dis[x][i]<=k)
		ret+=query1(fa[x][i],k-dis[x][i])-query2(fa[x][i+1],k-dis[x][i]);
	return ret;
}

int main(){
	n=rd(); m=rd(); int x,y,z;
	for(int i=1;i<=n;i++) val[i]=rd();
	for(int i=1;i<n;i++) x=rd(),y=rd(),add(x,y);
	mx=n+1; getroot(1,0,n); Divide(rt,n);
	for(int i=1;i<=n;i++) fa[i][dep[i]+1]=i;
	for(int i=1;i<=n;i++) change(i,val[i]);
	while(m--){
		x=rd(),y=rd()^ans,z=rd()^ans;
		if(!x) printf("%d\n",ans=ask(y,z));
		else change(y,z-val[y]),val[y]=z;
	}
	return 0;
}

bzoj4372: 烁烁的游戏
和上一题一模一样的思路,只不过相当于修改和询问换了一下,这样大概要单点修改查询后缀和,把树状数组的操作全反一下就行了,注意本身的值要手动修改因为树状数组下标不能为 0 0

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
using namespace std;

inline int rd(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}

int n,m,cnt,head[N],nxt[N<<1],to[N<<1],val[N];
int rt,mx,siz[N],dep[N],fa[N][20],dis[N][20];
vector<int> f[N],g[N];
bool vis[N];
char s[5];

inline void add(int x,int y){
	to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
	to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}

void getroot(int u,int fat,int tot){
	siz[u]=1; int maxx=0;
	for(int i=head[u],v;i;i=nxt[i])
		if(!vis[v=to[i]] && v!=fat){
			getroot(v,u,tot);
			siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
		}
	maxx=max(maxx,tot-siz[u]);
	if(maxx<mx) mx=maxx,rt=u;
}

void getdis(int u,int fat,int t,int d){
	for(int i=head[u],v;i;i=nxt[i])
		if(!vis[v=to[i]] && v!=fat){
			fa[v][++dep[v]]=t,dis[v][dep[v]]=d;
			getdis(v,u,t,d+1);
		}
}

void Divide(int root,int tot){
	vis[root]=1; getdis(root,0,root,1);
	f[root].resize(tot+1); g[root].resize(tot+1);
	for(int i=head[root],v;i;i=nxt[i])
		if(!vis[v=to[i]]){
			int now=siz[v]>siz[root]?tot-siz[root]:siz[v];
			mx=n+1; getroot(v,root,now);
			Divide(rt,now);
		}
}

inline int query1(int x,int k){int sum=0,lim=f[x].size()-1;for(;k&&k<=lim;k+=k&-k)sum+=f[x][k];return sum;}
inline int query2(int x,int k){int sum=0,lim=g[x].size()-1;for(;k&&k<=lim;k+=k&-k)sum+=g[x][k];return sum;}

inline void change(int x,int k,int w){
	val[x]+=w; if(!k) return;
	int d=k,lim=f[x].size()-1; d=min(d,lim);
	for(int i=d;i;i-=i&-i) f[x][i]+=w;
	for(int i=dep[x];i;i--)
		if(dis[x][i]<=k){
			val[fa[x][i]]+=w;
			d=k-dis[x][i],lim=f[fa[x][i]].size()-1; d=min(d,lim);
			for(int j=d;j;j-=j&-j) f[fa[x][i]][j]+=w;
			lim=g[fa[x][i+1]].size()-1; d=min(d,lim);
			for(int j=d;j;j-=j&-j) g[fa[x][i+1]][j]+=w;
		}
}

inline int ask(int x){
	int d,res=val[x];
	for(int i=dep[x];i;i--){
		d=dis[x][i];
		res+=query1(fa[x][i],d)-query2(fa[x][i+1],d);
	}
	return res;
}

int main(){
	n=rd(); m=rd(); int x,y,z;
	for(int i=1;i<n;i++) x=rd(),y=rd(),add(x,y);
	mx=n+1; getroot(1,0,n); Divide(rt,n);
	for(int i=1;i<=n;i++) fa[i][dep[i]+1]=i;
	while(m--){
		scanf("%s",s);
		if(s[0]=='Q') x=rd(),printf("%d\n",ask(x));
		else x=rd(),y=rd(),z=rd(),change(x,y,z);
	}
	return 0;
}

bzoj3924: [Zjoi2015]幻想乡战略游戏

窝发现那个记录每个节点往上的点分树上的节点的方法非常好用诶!
这个题就是发现假设 u u 为根,如果从 u &gt; v u-&gt;v ,则代价增量为 ( s u m u 2 s u m v ) d i s ( u , v ) (sum_u-2*sum_v)*dis(u,v) s u m u sum_u 就是以 u u 为根的子树中 d d 的和,那就是只有 2 s u m v &gt; s u m u 2*sum_v&gt;sum_u 的时候才能从 u u v v ,这样的 v v 一定只有一个,所以就可以从当前点分树的根开始往下找,统计答案就是再记录 s 1 , s 2 s1,s2 分别表示这个点子树的答案和这个点子树到它父亲的答案,然后每次暴力跳树高求答案就好了,如果求出这个 v v 的答案比 u u 更优,就直接跳到 v v 所在点分树的根,继续求就好了。
复杂度 O ( m l o g 2 n ) O(mlog^2n)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
#define LL long long
using namespace std;

template<class T>inline void rd(T &x){
	x=0; short f=1; char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	x*=f;
}
inline int max(int x,int y){return x>y?x:y;}

int n,m,cnt,head[N],nxt[N<<1],to[N<<1],w[N<<1];
int siz[N],dep[N],mx,rt,fat[N][20],Rt;
LL sum[N],s1[N],s2[N],dis[N][20];
bool vis[N];
vector<int> son[N],gson[N];

inline void add(int x,int y,int z){
	to[++cnt]=y,nxt[cnt]=head[x],w[cnt]=z,head[x]=cnt;
	to[++cnt]=x,nxt[cnt]=head[y],w[cnt]=z,head[y]=cnt;
}

void getroot(int u,int fa,int tot){
	siz[u]=1; int maxx=0;
	for(int i=head[u],v;i;i=nxt[i])
		if((v=to[i])!=fa && !vis[v]){
			getroot(v,u,tot);
			siz[u]+=siz[v];
			maxx=max(maxx,siz[v]);
		}
	maxx=max(maxx,tot-siz[u]);
	if(maxx<mx) mx=maxx,rt=u;
}

void getdis(int u,int fa,int t,LL d){
	fat[u][++dep[u]]=t,dis[u][dep[u]]=d;
	for(int i=head[u],v;i;i=nxt[i])
		if((v=to[i])!=fa && !vis[v])
			getdis(v,u,t,d+w[i]);
}

void Divide(int root,int tot){
	vis[root]=1; getdis(root,0,root,0);
	for(int i=head[root],v;i;i=nxt[i])
		if(!vis[v=to[i]]){
			int num=siz[v]<siz[root]?siz[v]:tot-siz[root];
			mx=n+1; getroot(v,root,num); 
			son[root].push_back(v); gson[root].push_back(rt);
			Divide(rt,num);
		}
}

inline void change(int v,int val){
	for(int i=dep[v],u;i;i--){
		u=fat[v][i]; sum[u]+=val; 
		s1[u]+=1LL*dis[v][i]*val; s2[u]+=1LL*dis[v][i-1]*val;
	} return;
}

inline LL calc(int u){
	LL ret=s1[u]; int x,y;
	for(int i=dep[u]-1;i>0;i--){
		x=fat[u][i],y=fat[u][i+1];
		ret+=s1[x]-s2[y]+1LL*(sum[x]-sum[y])*dis[u][i];
	} return ret;
}

inline LL ask(int u){
	LL ans=calc(u);
	for(int i=0,v;i<son[u].size();i++){
		v=son[u][i];
		if(calc(v)<ans) return ask(gson[u][i]);
	} return ans;
}

int main(){
	rd(n); rd(m); int x,y,z;
	for(int i=1;i<n;i++){rd(x),rd(y),rd(z);add(x,y,z);}
	mx=n+1; getroot(1,0,n); Rt=rt; Divide(rt,n);
	while(m--){
		rd(x),rd(y);
		change(x,y);
		printf("%lld\n",ask(Rt));
	}
	return 0;
}

bzoj1095: [ZJOI2007]Hide 捉迷藏
一道毒题,看了三天都没有一个好的解决方法只好去看题解。。。
维护三种堆:
1. f [ u ] f[u] 表示 u u 为根的子树中黑点到 G f a u Gfa_u 的距离的堆
2. g [ u ] g[u] 表示点分树中 u u 的子节点的 f f 堆顶
3. a n s ans 表示所有的 g g 中堆顶和次堆顶的和
那么答案就是 a n s . t o p ( ) ans.top()

考虑动态维护这三种堆,当把一个房间开关灯的时候,这个节点 u u f [ u ] f[u] 会改变,进而会改变 u u 在点分树上所有的父亲的 g g ,进而改变 a n s ans
所以每次先把 a n s ans g g 的部分删掉,然后把 g g f [ u ] f[u] 的堆顶删掉,修改 f [ u ] f[u] ,再修改 g g a n s ans

这里的堆用的是一个插入堆一个删除堆的那种操作

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define N 100005
#define LL long long
#define inf 0x3f3f3f3f
using namespace std;

template<class T>inline void rd(T &x){
	x=0; short f=1; char c=getchar();
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	x*=f;
}

int n,m,cnt,head[N],nxt[N<<1],to[N<<1],siz[N],val[N];
int rt,mx,dep[N],fat[N][20],dis[N][20];
bool vis[N];

struct Heap{
	priority_queue<int> a,b;
	inline void push(int v){a.push(v);}
	inline void del(int v){b.push(v);}
	inline bool empty(){while(!a.empty() && !b.empty() && a.top()==b.top()) a.pop(),b.pop(); return a.empty();}
	inline int size(){return a.size()-b.size();}
	inline void pop(){if(!empty()) a.pop();}
	inline int top(){if(!empty()) return a.top();return -inf;}
	inline int setop(){int tmp=top();pop();int ret=top();push(tmp);return ret;}
}f[N],g[N],ans;

inline void add(int x,int y){
	to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
	to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}

inline void insert(Heap &s){
	if(s.size()>=2) ans.push(s.top()+s.setop());
}
inline void erase(Heap &s){
	if(s.size()>=2) ans.del(s.top()+s.setop());
}

void getroot(int u,int fa,int tot){
	siz[u]=1; int maxx=0;
	for(int i=head[u],v;i;i=nxt[i])
		if((v=to[i])!=fa && !vis[v]){
			getroot(v,u,tot);
			siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
		}
	maxx=max(maxx,tot-siz[u]);
	if(maxx<mx) mx=maxx,rt=u;
}

void getdis(int u,int fa,int t,int d,Heap &s){
	s.push(d);
	fat[u][++dep[u]]=t,dis[u][dep[u]]=d;
	for(int i=head[u],v;i;i=nxt[i])
		if((v=to[i])!=fa && !vis[v])
			getdis(v,u,t,d+1,s);
}

void Divide(int root,int tot){
	vis[root]=1; g[root].push(0);
	for(int i=head[root],v;i;i=nxt[i])
		if(!vis[v=to[i]]){
			int now=siz[v]<siz[root]?siz[v]:tot-siz[root];
			mx=n+1; getroot(v,root,now);
			getdis(v,root,root,1,f[rt]);
			g[root].push(f[rt].top());
			Divide(rt,now);
		}
	insert(g[root]);
}

inline void turn_off(int u){
	erase(g[u]); g[u].push(0); insert(g[u]);
	for(int i=dep[u]+1;i>1;i--){
		int x=fat[u][i],y=fat[u][i-1];
		erase(g[y]);
		if(f[x].size()) g[y].del(f[x].top());
		f[x].push(dis[u][i-1]);
		if(f[x].size()) g[y].push(f[x].top());
		insert(g[y]);
	}
}

inline void turn_on(int u){
	erase(g[u]); g[u].del(0); insert(g[u]);
	for(int i=dep[u]+1;i>1;i--){
		int x=fat[u][i],y=fat[u][i-1];
		erase(g[y]);
		if(f[x].size()) g[y].del(f[x].top());
		f[x].del(dis[u][i-1]);
		if(f[x].size()) g[y].push(f[x].top());
		insert(g[y]);
	}
}

inline void change(int u){
	if(val[u]) val[u]=0,turn_off(u);
	else val[u]=1,turn_on(u);
}

int main(){
	rd(n); int x,y; char op[10];
	for(int i=1;i<n;i++) rd(x),rd(y),add(x,y);
	rd(m); mx=n+1; getroot(1,0,n); Divide(rt,n);
	for(int i=1;i<=n;i++) fat[i][dep[i]+1]=i;
	while(m--){
		scanf("%s",op);
		if(op[0]=='C') rd(x),change(x);
		else printf("%d\n",(x=ans.top())>0?x:-1);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/sizeof_you/article/details/85306426
今日推荐