点分治,点分树思路

点分治和点分树的核心思路都一样:找到树的重心,以重心为中心,把一棵树拆成若干规模更小的子树,递归处理。
但点分治是一种离线算法,通常用于统计树上满足某种距离条件的点对数量、距离和等问题。
点分树是一种在线算法,其将点分治过程中产生的重心关系保存下来,形成一棵新树,方便在线维护与查询。

点分治

点分治的核心思路就是:求子树重心->处理该重心的答案->递归到下个子树。

如何找树的重心

树的重心:树的重心是指删除该节点后,所有连通子树的节点数都不超过整棵树一半的节点的节点。
所以我们只要有子树大小,就可以很方便维护了。

点击查看代码
void get_sz(int u,int fa){
	sz[u]=1;
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(v==fa||del[v])continue;
		get_sz(v,u);
		sz[u]+=sz[v];
	}
}
int get_heavy_node(int u,int fa,int tot){
	for(int i=head[u],v;i;i=nxt[i]){ 		
		v=to[i];
		if(v==fa||del[v])continue;
		if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
	}
	return u;
}

时间复杂度简析 由于树的重心的性质,所以我们最多递归处理 $log(n)$ 层,每层连通块最多 $n$ 个节点,所以点分治本身的复杂度大约是$O(nlog(n))$

例题

统计树上距离不超过 \(k\) 的点对

给定一颗 \(n\) 个节点的无根树,对于树中的节点 \(u,v,u \neq v\),求 \(dis(u,v) \le k\) 的点对数。
P3806
P4178

思路

对于每个重心,统计路径过其的点对数,累和即可,记得容斥。

点击查看代码
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=1e5+5;
int head[N],nxt[N],to[N],cnt,val[N];
void add(int u,int v,int w){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
	val[cnt]=w;
	
	to[++cnt]=u;
	nxt[cnt]=head[v];
	head[v]=cnt;
	val[cnt]=w;
}
int sz[N];
bool del[N];
ll ans,n,k;
void get_sz(int u,int fa){
	sz[u]=1;
	for(int i=head[u];i;i=nxt[i]){
		if(to[i]==fa||del[to[i]])continue;
		get_sz(to[i],u);
		sz[u]+=sz[to[i]];
	}
}
int get_heavy_node(int u,int fa,int tot){
	for(int i=head[u];i;i=nxt[i]){
		if(to[i]==fa||del[to[i]])continue;
		if(sz[to[i]]>tot/2)return get_heavy_node(to[i],u,tot);
	}
	return u;
}
void collect_dis(int u,int fa,int disu,vector<int>&dis){//收集距离
	dis.push_back(disu);
	for(int i=head[u];i;i=nxt[i]){
		if(to[i]==fa||del[to[i]])continue;
		collect_dis(to[i],u,disu+val[i],dis);
	}
}
ll get_ans(vector<int>&dis){//答案计算
	sort(dis.begin(),dis.end());
	ll res=0;
	int l=0,r=dis.size()-1;
	while(l<r){
		if(dis[l]+dis[r]<=k){
			res+=r-l;
			l++;
		}
		else r--;
	}
	return res;
}
void node_divide(int u){
	get_sz(u,-1);
	int heavy_node_u=get_heavy_node(u,-1,sz[u]);
	del[heavy_node_u]=1;
	vector<int>dis;
	vector<int>son_tr_dis;
	dis.push_back(0);
	for(int i=head[heavy_node_u];i;i=nxt[i]){
		if(del[to[i]])continue;
		son_tr_dis.clear();
		collect_dis(to[i],heavy_node_u,val[i],son_tr_dis);
		ans-=get_ans(son_tr_dis);//容斥减去儿子节点的答案
		dis.insert(dis.end(),son_tr_dis.begin(),son_tr_dis.end());
	}
	ans+=get_ans(dis);//加上自身答案
	for(int i=head[heavy_node_u];i;i=nxt[i]){
		if(!del[to[i]])node_divide(to[i]);//分治
	}
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		add(u,v,w);
	}
	cin>>k;
	node_divide(1);
	cout<<ans;
	return 0;
}

有以上易推:树上距离等于 $k$ 的点对,树上距离是否存在距离为 $k$ 的点对,树上带权距离不超过 $k$ 的点对

统计树上距离模 \(3\) 为 \(0\) 的点对数

给定一颗 \(n\) 个节点的无根树,对于树中的节点 \(u,v,u \neq v\),求 \(dis(u,v)=0(mod 3)\) 的点对数。
每个结点记录模 \(3\) 的路径数即可。
P2634

点击查看代码
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=1e5+5;
ll head[N],to[N],nxt[N],cnt,val[N],t[3],f[3],num,ans;
void add(int u,int v,int w){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
	val[cnt]=w;
	
	to[++cnt]=u;
	nxt[cnt]=head[v];
	head[v]=cnt;
	val[cnt]=w;
}
ll sz[N],del[N],n;
void get_sz(int u,int fa){
	sz[u]=1;
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(v==fa||del[v])continue;
		get_sz(v,u);
		sz[u]+=sz[v];
	}
}
int get_heavy_node(int u,int fa,int tot){
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(v==fa||del[v])continue;
		if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
	}
	return u;
}
void get_dis(int u,int fa,int dis){
	t[dis]++;
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(del[v]||v==fa)continue;
		get_dis(v,u,(dis+val[i])%3);
	}
}
void node_divide(int u){
	get_sz(u,-1);
	int hnu=get_heavy_node(u,-1,sz[u]);
	del[hnu]=1;
	memset(f,0,sizeof(f));
	f[0]=1;
	for(int i=head[hnu],v;i;i=nxt[i]){
		v=to[i];
		if(del[v])continue;
		memset(t,0,sizeof(t));
		get_dis(v,hnu,val[i]%3);
		ans+=f[0]*t[0]+f[1]*t[2]+f[2]*t[1];
		f[0]+=t[0];
		f[1]+=t[1];
		f[2]+=t[2];
	}
	for(int i=head[hnu],v;i;i=nxt[i]){
		v=to[i];
		if(del[v])continue;
		node_divide(v);
	}
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		add(u,v,w);
	}
	node_divide(1);
	num=n*n;//总方案数
	ans*=2;//无序点对
	ans+=n;//本身
	ll gcdd=__gcd(ans,num);
	cout<<ans/gcdd<<'/'<<num/gcdd;
	return 0;
}

点分树

点分树就是点分治把重心拎出来建一棵树,方便动态修改。

例题

求带修改点权时,距离某点距离不超过 \(k\) 的点权和。

P6329

思路

配上树状数组即可方便修改。

点击查看代码
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=3e5+5;
struct QwQ{
	int first,second,third;
};
struct BIT{
	vector<int>dis,tr;
	void init(){
		sort(dis.begin(),dis.end());
		dis.erase(unique(dis.begin(),dis.end()),dis.end());
		tr.assign(dis.size()+1,0);
	}
	void add(int u,int v){
		int pos=lower_bound(dis.begin(),dis.end(),u)-dis.begin()+1;
		for(;pos<(int)tr.size();pos+=lowbit(pos)){
			tr[pos]+=v;
		}
	}
	int que(int u){
		int pos=upper_bound(dis.begin(),dis.end(),u)-dis.begin();
		int res=0;
		for(;pos;pos-=lowbit(pos)){
			res+=tr[pos];
		}
		return res;
	}
}all[N],sub[N];//all是所有节点到重心的点权,sub是重心的分支内到重心的点权。
vector<QwQ>path[N];
int head[N],to[N],nxt[N],cnt;
int sz[N],dep[N],del[N],up[N][30],fa[N],tot;
int n,m,val[N],lastans;
void add(int u,int v){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
	
	to[++cnt]=u;
	nxt[cnt]=head[v];
	head[v]=cnt;
}
void get_sz(int u,int fa){
	sz[u]=1;
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(v==fa||del[v])continue;
		get_sz(v,u);
		sz[u]+=sz[v];
	}
}
int get_heavy_node(int u,int fa,int tot){
	for(int i=head[u],v;i;i=nxt[i]){ 		
		v=to[i];
		if(v==fa||del[v])continue;
		if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
	}
	return u;
}
void get_path(int u,int fa,int node,int dis,int way){
	path[u].push_back({node,dis,way});
	all[node].dis.push_back(dis);
	sub[way].dis.push_back(dis);
	for(int i=head[u],v;i;i=nxt[i]){ 		
		v=to[i];
		if(v==fa||del[v])continue;
		get_path(v,u,node,dis+1,way);
	}
}//预处理出来 dis,way一系列信息
void build(int u,int fa_){
	get_sz(u,0);
	int h_n_u=get_heavy_node(u,0,sz[u]);
	del[h_n_u]=1;
	fa[h_n_u]=fa_;
	path[h_n_u].push_back({h_n_u,0,0});
	all[h_n_u].dis.push_back(0);
	for(int i=head[h_n_u],v;i;i=nxt[i]){
		v=to[i];
		if(del[v])continue;
		get_path(v,h_n_u,h_n_u,1,++tot);
	}
	for(int i=head[h_n_u],v;i;i=nxt[i]){         
		v=to[i];
		if(v==fa_||del[v])continue;
		build(v,h_n_u);
	}
}//建树
void dfs_lca(int u,int fa){
	dep[u]=dep[fa]+1;
	up[u][0]=fa;
	for(int i=1;i<=25;i++)up[u][i]=up[up[u][i-1]][i-1];
	for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v!=fa)dfs_lca(v,u);}
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	int dis=dep[u]-dep[v];
	for(int i=0;i<=25;i++){
		if((dis>>i)&1)u=up[u][i];
	}
	if(u==v)return u;
	for(int i=25;i>=0;i++){
		if(up[u][i]!=up[v][i]){
			u=up[u][i];
			v=up[v][i];
		}
	}
	return up[u][0];
}
int dis(int u,int v){
	return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd(int u,int v){
	for(auto [node,dis1,way]:path[u]){
		all[node].add(dis1,v);
		if(way)sub[way].add(dis1,v);
	}
}
int que(int u,int k){
	int ans=0;
	for(auto[node,dis1,way]:path[u]){
		if(dis1>k)continue;
		ans+=all[node].que(k-dis1);
		if(way)ans-=sub[way].que(k-dis1);
	}
	return ans;
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>val[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		add(u,v);
	}
	dfs_lca(1,0);
	build(1,0);
	for(int i=1;i<=n;i++)all[i].init();
	for(int i=1;i<=tot;i++)sub[i].init();
	for(int i=1;i<=n;i++){
		upd(i,val[i]);
	}
	for(int i=1,op,k,x;i<=m;i++){
		cin>>op>>x>>k;
		x^=lastans;
		k^=lastans;
		if(op==0){
			lastans=que(x,k);
			cout<<lastans<<'\n';
		}
		else{
			upd(x,k-val[x]);
			val[x]=k;
		}
	}
	return 0;
}

求所有标记点中,最远的两点距离。
P2056
思路
非常简单哈,维护距离即可,要可修改,可删除,用multiset即可。

点击查看代码
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=2e5+5;
int head[N],to[N],nxt[N],cnt;
int fa[N],up[N][30],sz[N],dep[N],del[N];
int n,q,num,val[N];
bool co[N];
vector<pii>path[N];
char op;
multiset<int>pq[N],pq2[N],ans;//pq到其父亲fa的距离,pq2表示他所有分支的pq中的最大值,便于容斥,ans是所有答案的集合
void add(int u,int v){
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
	
	to[++cnt]=u;
	nxt[cnt]=head[v];
	head[v]=cnt;
}
void get_sz(int u,int fa){
	sz[u]=1;
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(fa==v||del[v])continue;
		get_sz(v,u);
		sz[u]+=sz[v];
	}
}
int get_heavy_node(int u,int fa,int tot){
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(to[i]==fa||del[v])continue;
		if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
	}
	return u;
}
void get_path(int u,int fa,int c,int d){
	path[u].push_back({c,d});
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(fa==v||del[v])continue;
		get_path(v,u,c,d+1);
	}
}
void build(int u,int fa_){
	get_sz(u,0);
	int heavy_node_u=get_heavy_node(u,0,sz[u]);
	fa[heavy_node_u]=fa_;
	get_path(heavy_node_u,0,heavy_node_u,0);
	del[heavy_node_u]=1;
	for(int i=head[heavy_node_u],v;i;i=nxt[i]){
		v=to[i];
		if(!del[v])build(v,heavy_node_u);
	}
}
void dfs_lca(int u,int fa){
	dep[u]=dep[fa]+1;
	up[u][0]=fa;
	for(int i=1;i<=25;i++)up[u][i]=up[up[u][i-1]][i-1];
	for(int i=head[u],v;i;i=nxt[i]){
		v=to[i];
		if(v!=fa)dfs_lca(v,u);
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	int dis=dep[u]-dep[v];
	for(int i=0;i<=25;i++){
		if(dis&1)u=up[u][i];
		dis>>=1;
	}
	if(u==v)return u;
	for(int i=25;i>=0;i--){
		if(up[u][i]!=up[v][i]){
			u=up[u][i];
			v=up[v][i];
		}
	}
	return up[u][0];
}
int dis(int u,int v){
	return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd_ans(int x){
	int old=val[x];
	if(old!=-1)ans.erase(ans.lower_bound(old));
	if(pq2[x].size()==0||pq2[x].size()==1)val[x]=-1;
	else{
		auto it=pq2[x].rbegin();
		val[x]=*it;
		it++;
		val[x]+=*it;
	}
	if(val[x]!=-1)ans.insert(val[x]);
}
void add_node(int x){
	co[x]=1;
	num++;
	int pre=0;
	for(auto it=path[x].rbegin();it!=path[x].rend();++it){
		int i=it->first,d=it->second;
		if(!pre){
			pq2[i].insert(0);
			upd_ans(i);
		}else{
			int old_top=-1,new_top=-1;
			if(!pq[pre].empty())old_top=*pq[pre].rbegin();
			pq[pre].insert(d);
			if(!pq[pre].empty())new_top=*pq[pre].rbegin();
			if(old_top!=new_top){
				if(old_top!=-1){
					auto jt=pq2[i].find(old_top);
					if(jt!=pq2[i].end())pq2[i].erase(jt);
				}
				if(new_top!=-1)pq2[i].insert(new_top);
				upd_ans(i);
			}
		}
		pre=i;
	}
}
void del_node(int x){
	co[x]=0;
	num--;
	int pre=0;
	for(auto it=path[x].rbegin();it!=path[x].rend();++it){
		int i=it->first,d=it->second;
		if(!pre){
			auto jt=pq2[i].find(0);
			if(jt!=pq2[i].end())pq2[i].erase(jt);
			upd_ans(i);
		}else{
			int old_top=-1,new_top=-1;
			if(!pq[pre].empty())old_top=*pq[pre].rbegin();
			auto jt=pq[pre].find(d);
			if(jt!=pq[pre].end())pq[pre].erase(jt);
			if(!pq[pre].empty())new_top=*pq[pre].rbegin();
			if(old_top!=new_top){
				if(old_top!=-1){
					auto kt=pq2[i].find(old_top);
					if(kt!=pq2[i].end())pq2[i].erase(kt);
				}
				if(new_top!=-1)pq2[i].insert(new_top);
				upd_ans(i);
			}
		}
		pre=i;
	}
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	memset(val,-1,sizeof(val));
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		add(u,v);
	}
	dfs_lca(1,0);
	build(1,0);
	for(int i=1;i<=n;i++)add_node(i);
	cin>>q;
	for(int i=1,x;i<=q;i++){
		cin>>op;
		if(op=='C'){
			cin>>x;
			if(co[x])del_node(x);
			else add_node(x);
		}
		else{
			if(num==0)cout<<"-1\n";
			else if(num==1)cout<<"0\n";
			else cout<<*ans.rbegin()<<'\n';
		}
	}
	return 0;
}

posted @ 2026-10-18 20:56  c_z_y  阅读(4)  评论(0)    收藏  举报