点分治入门题……
因为对于一个点,只有两种情况,路径经过当前点OR不经过……
第二种情况等价于递归求解……
设dep[i]为第i个点到当前重心的深度,
对于经过的情况,即不在一颗子树(或只在一颗)的合法路径,即对于满足dep[i]+dep[j]<=K的i、j的总方案数,减去满足路径有重合(如下图有色路径)的方案数。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 99999999999
#define maxn 10000+10
#define For(x) for (int k=head[x];k;k=b[k].next)
using namespace std;
struct xx{
int v,next,q;
}b[maxn<<1];
int vis[maxn],head[maxn],//vis是否work完i点
f[maxn],//i点删掉后最多点数的联通块的点数
sz[maxn],//tree.size
d[maxn],//i点到重心的深度
dep[maxn],//存在当前子树的d[]
sum,ans,rot,n,m,K,x,y,z;
void add(int u,int v,int q)
{
b[++m]=(xx){v,head[u],q};
head[u]=m;
}
void getroot(int x,int fa)//找重心
{
sz[x]=1;f[x]=0;
For(x)
{
int v=b[k].v;
if (v==fa||vis[v])continue;
getroot(v,x);
sz[x]+=sz[v];
f[x]=max(f[x],sz[v]);
}
f[x]=max(f[x],sum-sz[x]);//找删掉x点后最多点数联通块的点数
rot=f[x]<f[rot]?x:rot;
}
void getdeep(int x,int fa)//找出当前子树所有点到重心的距离
{
dep[++dep[0]]=d[x];
For(x)
{
int v=b[k].v;
if (v==fa||vis[v])continue;
d[v]=d[x]+b[k].q;
getdeep(v,x);
}
}
int cal(int x,int now)//now为当前子树根到父亲的路径权值
{
d[x]=now;dep[0]=0;
getdeep(x,0);
sort(dep+1,dep+1+dep[0]);
int t=0,l,r;
for (l=1,r=dep[0];l<r;)
dep[l]+dep[r]<=K?t+=r-l,l++:r--;
return t;
}
void work(int x)//分治
{
vis[x]=1;
ans+=cal(x,0);
For(x)
{
int v=b[k].v;
if (vis[v])continue;
ans-=cal(v,b[k].q);//减去重复
sum=sz[v];
rot=0;
getroot(v,x);
work(rot);
}
}
int main()
{
while (1)
{
scanf("%d%d",&n,&K);
memset(vis,0,sizeof(vis));//初始化
memset(head,0,sizeof(head));
m=ans=rot=0;
if (n==0&&K==0) break;
for (int i=1;i<n;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
sum=n;f[0]=INF;
getroot(1,0);
work(rot);
printf("%d\n",ans);
}
return 0;
}