学习笔记第六十节:动态点分治

本文深入探讨了动态点分治的实现细节,介绍了如何通过维护点分治树和使用线段树来处理与点距离相关的查询,同时分享了一个具体的应用案例。

正题

      以前口胡了好多发的动态点分治,写起来的时候才知道有多恶心.

      其实很多动态点分治的题都很板子,但是都很难写,因为要将自己的信息传给儿子,对于每个点要维护自己的信息和子树在父亲中的信息.

      动态点分治实际上就是把点分治的那棵分治树保存下来,在题目不改变树的形态的情况下,可以通过对于每一个点维护一个数据结构来满足将点分治可以做的事情动态化,每次带个log.(分治树最大高度log

      像这题:【模板】点分树 | 震波

      对于每一个点,我们就用线段树维护自己子树内与该点距离为k的点权,并用一棵线段树,维护自己子树内的点与父亲的距离为k的点权,这样我们就可以实现将贡献区分了,每次询问查询x点与祖先的距离,并用k-距离到线段树查询即可.

      写起来也是非常的恶心,用了\O(n\log n)-\O(1)的求距离.

     

#include<bits/stdc++.h>
using namespace std;

const int N=200010;
struct edge{
	int y,nex;
}s[N<<1];
int first[N],len=0,T;
int rt[N][2],n,m,d[N],root,sz,mx=0,siz[N];
int sum[N*160],ls[N*160],rs[N*160],fa[N];
int son[N],tot[N],ans;
bool tf[N];
vector<int> P;

struct Lowest_Common_Ancestor{
	int dep[N],dfn[N],lg[N<<1],tim;
	int mmin[N<<1][20];
	void dfs(int x,int fa){
		dfn[x]=++tim;mmin[tim][0]=x;
		for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
			dep[s[i].y]=dep[x]+1;
			dfs(s[i].y,x);
			tim++;mmin[tim][0]=x;
		}
	}
	void pre(){
		lg[1]=0;for(int i=2;i<=2*n;i++) lg[i]=lg[i/2]+1;
		dep[1]=1;dfs(1,0);
		for(int i=2*(n-1);i>=1;i--){
			for(int k=1;k<=19;k++) if(i+(1<<k)-1<=2*(n-1)){
				if(dep[mmin[i][k-1]]<dep[mmin[i+(1<<(k-1))][k-1]]) mmin[i][k]=mmin[i][k-1]; 
				else mmin[i][k]=mmin[i+(1<<(k-1))][k-1];
			}
			else break;
		}
	}
	int Lca(int x,int y){
		if(dfn[x]>dfn[y]) swap(x,y);
		int tmp=lg[dfn[y]-dfn[x]+1];
		int a=mmin[dfn[x]][tmp],b=mmin[dfn[y]+1-(1<<tmp)][tmp];
		return dep[a]<dep[b]?a:b;
	}
	int gdis(int x,int y){return dep[x]+dep[y]-2*dep[Lca(x,y)];}
}LCA;

void ins(int x,int y){s[++len]=(edge){y,first[x]};first[x]=len;}
void fdrt(int x,int fa){
	son[x]=0;tot[x]=1;
	for(int i=first[x];i!=0;i=s[i].nex) if(!tf[s[i].y] && s[i].y!=fa){
		fdrt(s[i].y,x);
		tot[x]+=tot[s[i].y];
		if(tot[s[i].y]>tot[son[x]]) son[x]=s[i].y;
	}
	if(max(tot[son[x]],sz-tot[x])<max(tot[son[root]],sz-tot[root])) root=x;
}

void gi(int&now,vector<int>&S,int l,int r){
	if(!now) now=++T;
	if(l==r){sum[now]=S[l];return ;}
	int mid=(l+r)/2;
	gi(ls[now],S,l,mid);
	gi(rs[now],S,mid+1,r);
	sum[now]=sum[ls[now]]+sum[rs[now]];
}

void gp(int x,int dep,int fa){
	sz++;P.push_back(0);P[dep]+=d[x];
	for(int i=first[x];i!=0;i=s[i].nex) if(!tf[s[i].y] && s[i].y!=fa)
		gp(s[i].y,dep+1,x);
}

void build(int x,int tp){
	root=0;fdrt(x,0);tf[x=root]=true;siz[x]=sz;fa[x]=tp;
	if(siz[x]!=n) gi(rt[x][0],P,0,siz[x]);
	vector<int> S;S.resize(siz[x]+1);S.clear();S[0]=d[x];
	for(int i=first[x];i!=0;i=s[i].nex) if(!tf[s[i].y]){
		P.resize(1);sz=0;gp(s[i].y,1,x);
		for(int j=0;j<=sz;j++) S[j]+=P[j];
		build(s[i].y,x);
	}
	gi(rt[x][1],S,0,siz[x]);
}

int gs(int now,int x,int y,int l,int r){
	if(x==l && y==r) return sum[now];
	int mid=(l+r)/2;
	if(y<=mid) return gs(ls[now],x,y,l,mid);
	else if(mid<x) return gs(rs[now],x,y,mid+1,r);
	else return gs(ls[now],x,mid,l,mid)+gs(rs[now],mid+1,y,mid+1,r);
}

void add(int now,int x,int d,int l,int r){
	sum[now]+=d;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) add(ls[now],x,d,l,mid);
	else add(rs[now],x,d,mid+1,r);
}

void solve(int x,int y){
	int now=x,dis=0;ans=0;
	while(now){
		if(dis<=y) ans+=gs(rt[now][1],0,min(siz[now],y-dis),0,siz[now]);
		if(fa[now]){
			dis=LCA.gdis(fa[now],x);
			if(dis<=y) ans-=gs(rt[now][0],0,min(siz[now],y-dis),0,siz[now]);
		}
		now=fa[now];
	}
	printf("%d\n",ans);
}

void chg(int x,int y){
	int now=x,dis=0;
	while(now){
		add(rt[now][1],dis,y-d[x],0,siz[now]);
		if(fa[now]){
			dis=LCA.gdis(fa[now],x);
			add(rt[now][0],dis,y-d[x],0,siz[now]);
		}
		now=fa[now];
	}
	d[x]=y;
}

int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&d[i]);
	int type,x,y;
	for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
	LCA.pre();sz=n;build(1,0);
	while(m--){
		scanf("%d %d %d",&type,&x,&y);
		x^=ans;y^=ans;
		if(type==0) solve(x,y);
		else chg(x,y);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值