点分治第一题。
点分治主要用于统计满足某一性质的链或其数量。其主要思想是找重心->删重心->迭代子树。
每次迭代可以将子树分成近似相等两部分,这样就能处理两种类型的链:横跨重心和在子树内。
这样就可以在 O(nlogn) 的时间内找出所有的链,并进行处理。
这题统计和,所以要排序,因此这题的复杂度是 O(nlog2n) 。
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=1e4+7;
bool del[N];
int d[N],f[N],sz[N],ans,size,root,n,k;
struct Edge
{
int v,c;
Edge(int v,int c) : v(v),c(c) {}
Edge();
};
vector<Edge> adj[N];
vector<int> dep;
inline void addedge(int u,int v,int c)
{
adj[u].push_back(Edge(v,c));
adj[v].push_back(Edge(u,c));
}
// f[u]的含义为以u为root的情况下,所有子树中大小最大的那棵sz
// 重心就是 f 最小的那个点
void getroot(int u,int p)
{
sz[u]=1;f[u]=0;
for(int i=0;i<adj[u].size();++i)
{
int v=adj[u][i].v;
if(v==p||del[v]) continue;
getroot(v,u);
sz[u]+=sz[v];
f[u]=max(f[u],sz[v]);
}
f[u]=max(f[u],size-sz[u]);
if(f[u]<f[root]) root=u;
}
void getdeep(int u,int p)
{
dep.push_back(d[u]);
sz[u]=1;
for(int i=0;i<adj[u].size();++i)
{
int v=adj[u][i].v,c=adj[u][i].c;
if(del[v]||v==p) continue;
d[v]=d[u]+c;
getdeep(v,u);
sz[u]+=sz[v];
}
}
int cal(int u,int init)
{
dep.clear();d[u]=init;
getdeep(u,0);
sort(dep.begin(),dep.end());
int res=0;
int l=0,r=dep.size()-1;
while(l<=r)
{
if(dep[l]+dep[r]<=k) res+=r-l,++l;
else --r;
}
return res;
}
// 横跨u的两条链都能分成两段,因此对这两段进行双指针处理
// 后面减去在同一个子树中的两段形成的部分。
void solve(int u)
{
ans+=cal(u,0);
del[u]=true;
for(int i=0;i<adj[u].size();++i)
{
int v=adj[u][i].v;
if(del[v]) continue;
ans-=cal(v,adj[u][i].c);
f[0]=size=sz[v];
root=0;
getroot(v,0);
solve(root);
}
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
if(n==0&&k==0) break;
for(int i=1;i<=n;++i) adj[i].clear();
for(int i=1;i<n;++i)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
addedge(u,v,c);
}
ans=0;
memset(del,0,sizeof(del));
f[root=0]=size=n;
getroot(1,0);
solve(root);
printf("%d\n",ans);
}
return 0;
}