题意:给出一颗有 个点的树。给定一个 ,求
点分治裸题
考虑以一颗 为根的树,要统计其中长度 路径数量的和 。
如果直接打算全部算出来显然不怎么现实,我们先只考虑经过 的那些路径。
记 。有
设集合
如何求 ?
算出所有的 ,排序。
对于每个 求 满足 那么 的贡献就是
显然,如果 增大, 一定不会增大,所以可以 统计贡献
也可以尺取法。
现在考虑不经过 的那些路径。
去掉了 之后,每个以它的儿子为根的子树可以分别单独提出来考虑(分治)
如果直接沿着儿子走下去会爆炸(链的情况很显然),所以就需要考虑有没有更好的方法
复杂度显然跟每一步的子树大小有关,于是把这个作为标准考虑每步让子树大小至少 吧
也就是说要尽量平均地分配子树,更直观地讲就是让最大子树最小
所以理想的分治方案就是找重心提根来 。
总的复杂度为
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
using namespace std;
int N,K,tot=0,root,mn;
int head[10005]={},nxt[20005]={},to[20005]={},val[20005]={};
bool vis[20005]={};
int siz[10005]={},mxsiz[10005]={},curtotal=0,ans=0;
int dis[10005]={};
#define add_edge(a,b,c) nxt[++tot]=head[a],head[a]=tot,to[tot]=b,val[tot]=c;
void getdis(int x,int fa,int d)
{
dis[++dis[0]]=d;
for(int v,i=head[x];i;i=nxt[i])
{
v=to[i]; if(vis[v]||v==fa)continue;
getdis(v,x,d+val[i]);
}
}
int solve(int x,int add)
{
dis[0]=0;
getdis(x,0,add);
sort(dis+1,dis+1+dis[0]);
int ret=0,iter=dis[0];
for(int i=1;i<=dis[0];++i)
{
while(dis[iter]+dis[i]>K)--iter;
if(iter<=i)break;
ret+=iter-i;
}
return ret;
}
void getroot(int x,int fa)
{
siz[x]=1; mxsiz[x]=0;
for(int v,i=head[x];i;i=nxt[i])
{
v=to[i]; if(vis[v]||v==fa)continue;
getroot(v,x); siz[x]+=siz[v];
mxsiz[x]=max(mxsiz[x],siz[v]);
}
mxsiz[x]=max(mxsiz[x],curtotal-siz[x]);
if(mxsiz[x]<mn)mn=mxsiz[x],root=x;
}
void divide(int x)
{
ans+=solve(x,0);
vis[x]=1;
for(int v,i=head[x];i;i=nxt[i])
{
v=to[i]; if(vis[v])continue;
ans-=solve(v,val[i]);
curtotal=siz[v]; mn=2147483647; getroot(v,0);
divide(root);
}
}
int main()
{
while(~scanf("%d%d",&N,&K))
{
if((!N)&&(!K))return 0;
memset(vis,0,sizeof(vis));
ans=0,tot=0,mn=2147483647,curtotal=N;
for(int i=1;i<=N;++i)head[i]=0;
for(int u,v,w,i=1;i<N;++i)
{
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
getroot(N>>1,0);
divide(root);
printf("%d\n",ans);
}
return 0;
}