点分治

点分治

给定一棵带权树,求树上路径小于等于k,求简单路径条数,不可以有重边;
显然,对于每个点来说,有两种情况
1,过这个点的路径;
2,不过这个点的路径;
对于第二种情况,显然会经过其他节点,为了避免算重,每次算完之后把当前点删去;
为了使删完后的树尽量平衡,可以选择重心;
对于1这种情况,以选出来的重心为根节点,遍历一遍树;
算出其他点到root的距离d[i],从小到大排序,用双指针;
左端点枚举l,右端点不断向左移,每次答案加上r-l,因为l自己本身不算;
由于题目要求简单路径,所以如果其中有两个点在同一个子树中,不符合情况;
可以用容斥解决,可以单独算在每个子树的情况,用总答案减去即可;
对于第二种情况,遍历删去当前节点的每一个棵子树,重复1过程;

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=4e4+7;
const int inf=0x3f3f3f3f; 
struct edge{
	int v,w,nxt;
}e[N<<1];
int n,k,now_siz,sz,root,ans,cnt,tot;
int head[N],d[N],st[N],siz[N],vis[N];
void add_edge(int u,int v,int w){
	e[++cnt]=(edge){v,w,head[u]};
	head[u]=cnt;
}
void get_root(int u,int fa){
	siz[u]=1;
	int res=0;
	for(int i=head[u];i;i=e[i].nxt){
		int to=e[i].v;
		if(vis[to]||to==fa) continue;
		get_root(to,u);
		siz[u]+=siz[to];
		res=max(res,siz[to]);
	}
	res=max(res,sz-siz[u]);
	if(res<now_siz) root=u,now_siz=res;
}
void get_dis(int u,int fa){
	st[++tot]=d[u];
	for(int i=head[u];i;i=e[i].nxt){
		int to=e[i].v;
		if(vis[to]||to==fa) continue;
		d[to]=d[u]+e[i].w;
		get_dis(to,u);
	}
}
int solve(int u,int dis){
	d[u]=dis;
	tot=0;
	get_dis(u,0);
	sort(st+1,st+tot+1);
//	for(int i = 1;i <= tot; i++) cout << st[i] << " ";
//	cout <<endl;
	int l=1,r=tot,res=0;
	for(;l<r;l++){
		while(st[r]+st[l]>k){
			r--;
		}
		if(l<r) res+=r-l;
	}
//	cout<<res<<"
";
	return res;
	
}
void dfs(int u){
	vis[u]=1;
//	cout<<"-->"<<u<<"
";
	ans+=solve(u,0);
	for(int i=head[u];i;i=e[i].nxt){
		int to=e[i].v;
		if(vis[to]) continue;
		ans-=solve(to,e[i].w);
		now_siz=inf,sz=siz[to],root=0;
		get_root(to,0);
		dfs(root);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add_edge(x,y,z);
		add_edge(y,x,z);
	}
	scanf("%d",&k);
	now_siz=inf,sz=n,root=0;
	get_root(1,0);
	dfs(root);
	cout<<ans;
}
原文地址:https://www.cnblogs.com/Aswert/p/13599750.html