点分治

前言

浅学了下点分治,笔者不才 ,可能有很多地方都是错的 ,希望大家不吝赐教,前面的题解会尽量详细,后面会逐渐简短

正文

首先,点分治常被用来解决树上的路径问题,例如: 一棵树上长为 \(k\) 的路径个数, 或者是路径长度 \(\leq k\) 的路径条数等等

本文将随便举几个常见的题目以及对应解法(应该要持续更新)

luogu P3806 【模板】点分治

题目

题目大意: 给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在。

解题思路

乍一看我们很显然就有一个 \(\mathcal{O(n^2logn)}\) 的暴力方法来解决本题,思路是枚举点对然后使用 \(lca\) 进行计算

这个算法好像不能再继续优化了,于是我们切换思路,考虑在树上的路径都有些什么情况,发现不过三种:

  • 以根节点为其中一个端点的.
  • 只是经过根节点的.
  • 不经过根节点的.

我们发现其实前面两种可以算一种,因为我们只需要把经过根节点的路径从根节点的位置分开即可变为两个经过根节点的路径.

而至于我们的第三种路径则看起来有些困难,但是如果我们已经处理完了当前经过根节点的所有路径,此时所剩余的路径全都不经过根节点,于是我们考虑删除根节点.

删除完了之后剩下的图我们还是先考虑经过剩下的树的根节点的路径,考虑完之后删掉根节点...如此往复

这时我们就发现刚刚的方法已经可以解决所有的路径了,此处给出一个简单证明:

假设我们处理完之后删去根节点,处理剩下的子树以后还有剩余的路径没有处理,易知该路径一定经过刚才我们的根节点,与假设不符,故假设成立.

并且这个算法复杂度还在 \(\mathcal{O(n^2)}\) 比我们的暴力少了一个 \(\mathcal{log}\) 因为每次我们删去根节点都会使图分裂为 \(siz_u \leq n-1\) 的几个子图

为什么是 \(n-1\) :考虑整个图是一条链的情况,每次只会删去最顶上的节点而剩下还剩余 \(n-1\) 个节点需要依次处理

由于这是一颗无根树,我们考虑切换一个根,这个根不管是它的子树大小,还是除开它的子树剩余的大小,我们都希望它尽量的小这样可以使我们的时间复杂度降到最低,那么我们可以很显然地发现,这个子树最小它也只能降为原来的 \(\frac{1}{2}\) .因为你除了子树还有剩下的部分,最小的时候也就是两个相等的时候.

那么有没有这样一个点可以让我们做到这样一件事呢?

答案是有的,对于一棵树,我们有树的重心,恰好就可以满足我们剩下的两个子树都在 \(\frac{n}{2}\) 的级别.

于是我们的思路就比较清晰了,在每次处理前先 \(dp\)树的重心,然后将其作为根节点处理情况一和情况二,最后再在图中删去这个重心,继续处理.

那么对于情况一和情况二,由于它们是一定会经过当前子树的根节点的,并且我们发现路径最长也是 \(\leq 10^7\) 的,所以我们可以在处理每个子树的时候存一个桶来记录当前,从根节点出发的到已经遍历过的节点的路径长度

对于情况一我们直接判断有没有这样的一条路合题.

对于情况二我们直接检查桶里有没有剩下的与当前的点不在同一颗更小的子树并且相加合题的路径.

然后本题就被我们解决了.时间复杂度在 \(\mathcal{O(nmlogn)}\) 其中 $n\leq 10^4 $ $ m\leq 100 $ 足以通过本题

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=105;
const int V=1e7+5;
int n,m,tot,head[N],vis[N],dp[N],d[N],k[N],ans[N],siz[N],len[N],cnt,dis[N],num;
bool ok[V];
struct node{
	int to,nxt,w;
}edge[N<<1];
void add(int u,int v,int w){
	edge[++tot].to=v;
	edge[tot].nxt=head[u];
	edge[tot].w=w;
	head[u]=tot;
}

void dfs1(int u,int fa){
	siz[u]=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to,w=edge[i].w;
		if(vis[v]||v==fa)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
	}
}

void dfs2(int u,int fa,int rt,int& minx,int& g){
	int minu=siz[rt]-siz[u];
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to,w=edge[i].w;
		if(v==fa||vis[v])continue;
		minu=max(minu,siz[v]);
		dfs2(v,u,rt,minx,g);
	}
	if(minu<minx)minx=minu,g=u;
}

void dfs3(int u,int fa,int dep){
	if(dep<=1e7)len[++cnt]=dep;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to,w=edge[i].w;
		if(vis[v]||v==fa)continue;
		dfs3(v,u,dep+w);
	}
}

void solve(int u){
	cnt=0;num=0;
	dfs1(u,0);
	int minx=V,g;
	dfs2(u,0,u,minx,g);
	vis[g]=1;ok[0]=1;dis[g]=0;
	for(int i=head[g];i;i=edge[i].nxt){
		int j=edge[i].to,w=edge[i].w;
		if(vis[j])continue;
		cnt=0;
		dfs3(j,g,w);
		for(int u=1;u<=cnt;u++){
			for(int v=1;v<=m;v++){
				if(k[v]-len[u]>=0)ans[v]=max(ans[v],1*ok[k[v]-len[u]]);
			}
		}
		for(int u=1;u<=cnt;u++){
			dis[++num]=len[u];
			if(len[u]<V)ok[len[u]]=1;
		}
	}
	for(int i=1;i<=num;i++){
		if(dis[i]<V)ok[dis[i]]=0;
	}
	for(int i=head[g];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(!vis[v])solve(v);
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	for(int i=1;i<=m;i++)
		scanf("%d",&k[i]);
	solve(1);
	for(int i=1;i<=m;i++)
		printf(ans[i]? "AYE\n":"NAY\n");
	return 0;
}
posted @ 2026-07-14 18:56  dgcsjfs  阅读(7)  评论(0)    收藏  举报