学习了一下点分治。
与树链剖分的思想差不多,我们把分治策略应用到树上。
比如这个题,我们要求的是一些合法的路径,这些路径我们可以分为两种:过某个点和不过某个点。这样我们就可以分别来求这两种情况。
如果我们已经知道了此时所有点到根的距离d[i],d[x] + d[y] <= k的(x,y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉,因为在对子树进行求解的时候,会重新计算。
至于“某个点”的选择,为了保证复杂度,我们应选择树的重心。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 1000000007
using namespace std;
int n,k,cnt,sum,ans,root;
int head[40005],deep[40005],d[40005],f[40005],size[40005];
bool vis[40005];
int next[80005],list[80005],key[80005];
inline int read()
{
int a=0,f=1; char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
return a*f;
}
inline void insert(int x,int y,int z)
{
next[++cnt]=head[x];
head[x]=cnt;
list[cnt]=y;
key[cnt]=z;
}
void getroot(int x,int fa) //寻找树的重心
{
size[x]=1; f[x]=0;
for (int i=head[x];i;i=next[i])
if (list[i]!=fa&&!vis[list[i]])
{
getroot(list[i],x);
size[x]+=size[list[i]];
f[x]=max(f[x],size[list[i]]);
}
f[x]=max(f[x],sum-size[x]);
if (f[x]<f[root]) root=x;
}
void getdeep(int x,int fa)
{
deep[++deep[0]]=d[x];
for (int i=head[x];i;i=next[i])
if (list[i]!=fa&&!vis[list[i]])
{
d[list[i]]=d[x]+key[i];
getdeep(list[i],x);
}
}
inline int calc(int x,int now)
{
d[x]=now; deep[0]=0;
getdeep(x,0);
sort(deep+1,deep+deep[0]+1);
int t=0,l=1,r=deep[0];
while (l<r)
if (deep[l]+deep[r]<=k) t+=r-l,l++; else r--;
return t;
}
void work(int x)
{
ans+=calc(x,0);
vis[x]=1;
for (int i=head[x];i;i=next[i])
if (!vis[list[i]])
{
ans-=calc(list[i],key[i]);
sum=size[list[i]];
root=0;
getroot(list[i],0);
work(root);
}
}
int main()
{
n=read();
for (int i=1,u,v,w;i<n;i++)
u=read(),v=read(),w=read(),insert(u,v,w),insert(v,u,w);
k=read();
sum=n; f[0]=inf;
getroot(1,0);
work(root);
printf("%d\n",ans);
return 0;
}