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