POJ 1741(点分治)(模板)

传送门

题意:统计树上距离不超过K的点对数量

题解:点分治,求树的重心,统计有多少条经过重心的合法路径,再对子树进行相同操作(递归处理),总复杂度O(nlog(n)log(n))。为什么是这个复杂度?因为以重心为根的树至少有两棵子树,所以分治的层数不超过log(n)。每次统计时,设当前树的大小为size,那么排序的复杂度为size*log(size),双指针扫描为size,所以复杂度取size*log(size)。每一层分治的总复杂度为\sum size*log(size)<\sum size*log(n)<log(n)*\sum size=nlog(n),所以整个算法复杂度为nlog(n)log(n)。

注意点:

1. 每次统计时答案也包括了不经过重心的合法路径,所以要在枚举子节点时再算一次将其减去

2. 每次求重心的时树的大小要专门记为sum,不能用n代替,否则重心求错,答案虽然不会错但是会TLE

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e4+4;
const int INF=0x3f3f3f3f;
typedef long long ll;
int n,K;
int head[N],etot;
struct Edge {
	int v,nxt,w;
}e[N<<1];
int dis[N],siz[N],mx[N];
bool vis[N];
ll ans;
int root,s,sum;
int q[N],cnt;
inline void adde(int v,int u,int w) {
	e[++etot].nxt=head[u],e[etot].v=v,e[etot].w=w,head[u]=etot;
}
inline void init() {
	etot=0;
	ans=0;
	memset(head,-1,sizeof(head));
	memset(vis,false,sizeof(vis));
}
inline void smax(int &a,int b) {
	a=a>b?a:b;
}
inline void getroot(int p,int fa) {
	mx[p]=-INF,siz[p]=1;
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (vis[v]||v==fa) continue;
		getroot(v,p);
		siz[p]+=siz[v];
		smax(mx[p],siz[v]);
	}
	smax(mx[p],sum-siz[p]);
	if (mx[p]<s) s=mx[p],root=p;
}
inline void getdis(int p,int fa) {
	q[++cnt]=dis[p];
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (vis[v]||v==fa) continue;
		dis[v]=dis[p]+e[i].w;
		getdis(v,p);
	}
}
inline ll calc(int p,int d) {
	dis[p]=d;
	cnt=0;
	getdis(p,0);
	sort(q+1,q+cnt+1);
	int l=1,r=cnt;
	ll ret=0;
	while (l<r) {
		while (l<r&&q[r]+q[l]>K) --r;
		ret+=r-l;
		++l;
	}
	return ret;
}
inline void work(int p) {
	vis[p]=true;
	ans+=calc(p,0);
	for (int i=head[p];~i;i=e[i].nxt) {
		int v=e[i].v;
		if (vis[v]) continue;
		ans-=calc(v,e[i].w);
		s=INF,sum=siz[v];
		getroot(v,0);
		work(root);
	}
}
int main() {
//	freopen("in.txt","r",stdin);
	while (scanf("%d%d",&n,&K)&&(n||K)) {
		init();
		for (register int i=1;i<n;++i) {
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			adde(u,v,w);
			adde(v,u,w);
		}
		s=INF,sum=n;
		getroot(1,0);
		work(root);
		printf("%lld\n",ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值