点分治

(淀粉汁)

对于静态的树上路径统计问题,点分治可以通过\(O(n\logn)\)的遍历复杂度求解。大概的思想是分通过每个节点的路径来统计。

首先我们对于树上统计问题,考虑这样一个思路:从每个节点开始,每次统计完所有通过这个节点的路径之后,把这个点从树上删除,然后递归统计所有剩下的子树。重复该步骤,直到统计完毕。

这个思路显然是正确的,因为我们统计完通过一个节点的路径之后,相当于统计了所有不同子树之间的路径。之后,我们就只剩下了子树之内的路径了。所以我们直接递归向下就行了。

然而,我们考虑这个方法的复杂度时,发现:如果我们随意选取节点,那么它的复杂度应该是\(O(n^2)\)的。举个例子,我们给一条链,每次从一头开始,那么每次就要遍历整棵树。所以考虑一个简单的减小复杂度的方法。

我们每次递归开始的时候,找一下这课子树的重心。然后以重心为节点继续统计。这样,由于每棵子树的大小不超过原树的一半,根据主定理,我们就可以把复杂度变成\(O(n\log n)\)。(这是纯遍历,不算内部统计答案的复杂度)

bool v[40010];//每个节点是否被删除 
int rt,sum,cnt,dis[40010],size[40010],maxx[40010],a[40010];
//找重心 
void find(int x,int fa){
	size[x]=1;maxx[x]=0;
	for(int i=head[x];i;i=edge[i].next){
		if(!v[edge[i].v]&&edge[i].v!=fa){
			find(edge[i].v,x);
			size[x]+=size[edge[i].v];
			maxx[x]=max(maxx[x],size[edge[i].v]);
		}
	}
	maxx[x]=max(maxx[x],sum-size[x]);
	if(maxx[x]<maxx[rt])rt=x;
}
//两个统计答案的函数 怎么写看题 
//这里随便找了一个题的放上 
//大概就是遍历 一直爆搜 按照你觉得能解决问题的方法爆搜 
void getdis(int x,int fa){
	if(a[x]<=k)dis[++dis[0]]=a[x];
	for(int i=head[x];i;i=edge[i].next){
		if(!v[edge[i].v]&&edge[i].v!=fa){
			a[edge[i].v]=a[x]+edge[i].w;
			getdis(edge[i].v,x);
		}
	}
}
int solve(int x,int len){
	dis[0]=0;a[x]=len;cnt=0;
	getdis(x,0);
	sort(dis+1,dis+dis[0]+1);
	for(int l=1,r=dis[0];r>=l;l++){
		while(dis[l]+dis[r]>k)r--;
		if(r<l)break;
		cnt+=r-l;
	}
	return cnt;
}
//点分治部分  
void div(int x){
	ans+=solve(x,0);v[x]=true;
	for(int i=head[x];i;i=edge[i].next){
		if(!v[edge[i].v]){
			ans-=solve(edge[i].v,edge[i].w);//会在递归之后重复计算所以要减掉  
			sum=size[edge[i].v];rt=0;
			find(edge[i].v,x);div(rt);
		}
	}
}
//主函数内: 
sum=n;maxx[rt]=n+1;
find(1,0);div(rt);

点分树(待补)

posted @ 2022-09-03 11:41  gtm1514  阅读(27)  评论(0编辑  收藏  举报