题意:
给你一棵
n
n
n个点的树,求树上长度不超过
k
k
k的路径数。
n
<
=
40000
n<=40000
n<=40000
题解:
一看到这种处理树上路径的问题,经常是往点分治方面去想。这个题我们考虑点分治,然后在每个分治重心处计算子树里路径对答案的贡献。计算的方法是,你先求出子树里的所有路径的长度,然后排序一遍,用一个类似双指针的东西扫一遍,对于每个左端点,合法的右端点一定是一个连续的区间,并且随着左端点的增加,右端点位置单调递减。但是我们会发现,还是存在着点分治常见的两条路径来自同一个子树却对答案产生影响的情况。由于路径条数是有可减性的,所以我们直接对于每个子树再算一遍答案,减去每一个子树内部的答案,就是在当前分治重心对答案的贡献。排序+点分治,总复杂度是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,hed[50010],cnt,sz[50010],mx[50010],k,rt,vis[50010],dis[50010],q[50010],num;
long long ans;
struct node
{
int to,next,dis;
}a[200010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to,int dis)
{
a[++cnt].to=to;
a[cnt].dis=dis;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void getrt(int x,int f,int size)
{
sz[x]=1;
mx[x]=0;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y]||y==f)
continue;
getrt(y,x,size);
sz[x]+=sz[y];
mx[x]=max(mx[x],sz[y]);
}
mx[x]=max(mx[x],size-sz[x]);
if(mx[x]<mx[rt])
rt=x;
}
inline void dfs(int x,int f)
{
q[++num]=dis[x];
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(y==f||vis[y])
continue;
dis[y]=dis[x]+a[i].dis;
dfs(y,x);
}
}
inline long long calc(int x,int d)
{
dis[x]=d;
num=0;
dfs(x,0);
sort(q+1,q+num+1);
int l=1,r=num;
long long res=0;
while(l<r)//对于每个l算答案
{
if(q[l]+q[r]<=k)
{
res+=r-l;
++l;
}
else
--r;
}
return res;
}
inline void solve(int x,int size)
{
ans+=calc(x,0);
vis[x]=1;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y])
continue;
ans-=calc(y,a[i].dis);
}
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
if(vis[y])
continue;
int ji;
if(sz[y]<sz[x])
ji=sz[y];
else
ji=size-sz[x];
rt=0;
getrt(y,0,ji);
solve(y,ji);
}
}
int main()
{
n=read();
for(int i=1;i<=n-1;++i)
{
int x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
k=read();
mx[0]=1e9;
getrt(1,0,n);
solve(rt,n);
printf("%lld\n",ans);
return 0;
}