点分治

第一场 \(csp\) 后的第一篇博客,我已经 \(OIer\) 大半年了,再也不是之前那个轻狂的小朋友了。。。


所以考虑维护一种算法支持查询树上第 \(k\) 短的路径是否存在。

首先构造两个函数 \(\{dfsdist,dfssize\}\) , 当然其中 \(dfssize\) 主要是计算重心的。

这两个函数相对比较好些,其中 \(dfssize\) 函数需要一个辅助数组 \(max\),表示以 \(u\) 为根切割的最大的子树大小。还要注意作一个 \(sum\),表示递归的树整棵树的大小,然后直接用 \(rt\) 表示子树重心就好。

\(dist[i]\) 表示 \(i \rightarrow u\) 的距离。

这两个函数后面会辅助点分治的作用。

复杂度:

  • \(dfssize\) 的需要递归一遍整颗子树,复杂度也就是整颗子树的大小,可以简单地记作 \(\Theta(n)\) ,但是她并不是整个点分治复杂度瓶颈的一部分,所以这边并不需要在一这个函数的具体复杂度。

  • \(dfsdist\) 是同理的。

代码相对来说比较简单,如下。

void dfs1(int u,int f){
	sz[u]=1;
	max[u]=0;
	for(pii pr:G[u])
		if(!vis[v] && v^f){
			dfs1(v,u);
			sz[u]+=sz[v];
			max[u]=std::max(max[u],sz[v]);
		}
	max[u]=std::max(max[u],sum-sz[u]);
	if(max[u]<max[rt]) rt=u;
}

int dd[N],dist[N],tot;

void dfs2(int u,int f){
	dd[++tot] = dist[u];
	for(pii pr:G[u])
		if(v^f && !vis[v])
			dist[v]=dist[u]+w,dfs2(v,u);
	return;
}

当然,其中的 \(dfs1\) 是计算重心的,\(max[N]\) 数组是上文中的辅助数组。这里需要注意一定要计算一个 \(sum\) 的迭代,否则叶子节点一定会是最优解点,但是显然地,叶子节点不会是重心。


因为最开始的树是一颗无根树,非常不美丽,相当的不好处理。

于是我们钦定一个根 \(rt\) ,那么树上的所有路径可以分成两类:

  • 经过 \(rt\) 的路径

  • 未经过 \(rt\) 的路径

那么经过 \(rt\) 的路径又可以分为两类:

  • \(rt\) 为根的路径

  • 不以 \(rt\) 为根的路径

那么我们可以把上述的三种路径描述具象一点:

1

那么考虑:

钦定节点 \(1\) 为整棵树的根节点,那么路径

\(1\rightarrow 6\) 是以 \(rt\) 为根的路径

\(5\rightarrow 7\) 是未经过 \(rt\) 的路径

\(2\rightarrow 3\) 经过 \(rt\) 的路径

那么我们考虑来维护一下这三种路径。

  • 首先是路径 \(1\) ,通过 \(dist[i]\) 数组可以直接计算出 \(K\) 长度的路径是否存在。

  • 其次是路径 \(2\) ,通过计算 \(rt\) 为根的树的子树也是可以求解的。

  • 最后是路径 \(3\) ,可以考虑对于路径 \(1\) 进行链的合并,比如说路径 \(2\rightarrow 3\) 可拆分为 \(1\rightarrow 3 + 2\rightarrow 1\)

然后直接以 \(dfsdist\) 得到的数据进行处理即可, \(dfssize\) 可以选出树的重心,然后钦定树的重心为根,再进行操作就行了。

#include <bits/stdc++.h>
#define FOR(i,s,n) for(register int i=s;i<=n;++i)
#define ROF(i,n,s) for(register int i=n;i>=s;--i)
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r)>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
#define self rt,l,r
#define pii std::pair<int,int>
#define fir first
#define sec second
#define v (pr.fir)
#define w (pr.sec)
#define gc getchar()
#define re read()
#define add push_back
const int N=1e7+10;
const int maxn=1e7+5;
std::bitset<maxn> vis;
std::vector<pii> G[N];
std::stack<int> stack;
const int inf=1e9;
int sz[N],dfn[N],max[N];
int tf[N];
int rt,sum;
int Q[N],ans[N];
int n,m;

void dfs1(int u,int f){
	sz[u]=1;
	max[u]=0;
	for(pii pr:G[u])
		if(!vis[v] && v^f){
			dfs1(v,u);
			sz[u]+=sz[v];
			max[u]=std::max(max[u],sz[v]);
		}
	max[u]=std::max(max[u],sum-sz[u]);
	if(max[u]<max[rt]) rt=u;
}

int dd[N],dist[N],tot;

void dfs2(int u,int f){
	dd[++tot] = dist[u];
	for(pii pr:G[u])
		if(v^f && !vis[v])
			dist[v]=dist[u]+w,dfs2(v,u);
	return;
}

void dfs3(int u,int f){
	tf[0]=true;
	stack.push(0);
	vis[u]=true;
	for(pii pr:G[u]){
		if(v^f && !vis[v]){
			dist[v]=w;
			dfs2(v,u);
			for(register int k=1;k<=tot;++k){
				for(register int i=1;i<=m;++i){
					if(Q[i]>=dd[k]) ans[i] |= tf[Q[i]-dd[k]];
				}
			}
			for(register int k=1;k<=tot;++k)
				if(dd[k] < maxn) stack.push(dd[k]) , tf[dd[k]]=true;
			tot=0; 
		}
	}
	while(!stack.empty())
		tf[stack.top()]=false,stack.pop();
	for(pii pr:G[u])
		if(!vis[v] && v^f){
			sum=sz[v];
			rt=0;
			max[rt]=inf;
			dfs1(v,u);
			dfs1(rt,-1);
			dfs3(rt,u);
		}
}

int read(void){
	int res=0;
	char ch=0;
	while(!isdigit(ch)) ch=gc;
	while(isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=gc;
	return res;
}

int main(){
	n=re,m=re;
	for(register int i=1;i<n;++i)
		{
			int a,b,c;
			a=re,b=re,c=re;
			G[a].add({b,c});
			G[b].add({a,c});
		}
	for(register int i=1;i<=m;++i)
		Q[i] = re;
	sum=n;
	rt=0;
	max[rt]=inf;
	dfs1(1,-1);
	dfs1(rt,-1);
	dfs3(rt,-1);
	for(register int i=1;i<=m;++i)
		if(ans[i])
			puts("AYE");
		else 
			puts("NAY");
	exit(0);
}
复杂度分析:

递归层数最高 \(\log_2^2 n \times n \thickapprox n \log_2^2 n\)

菊花图会被卡爆,找错重心会被卡爆,爆炸了。

但是好像有优化办法,笔者正在寻找。

现在这里放一句 \(cnblog\) 上面对复杂度的分析

若我们 每次选择子树的重心作为根节点,可以保证递归层数最少,时间复杂度为 O(n\log n)。

posted @ 2023-10-23 17:36  q(x)  阅读(21)  评论(0编辑  收藏  举报