树分治(点分治)
点分治主要能解决的问题是树上路径个数的问题。
分治也并没有想象中的那么难,就是时间复杂度有点玄学,有的人说是nlog^2n有的说是nlogn,我也不知道具体是多少(反正比n^2暴力快就是了)
点分治的思想就是每次取一个重心,然后把每个经过该重心的路径数量算出来(要用容斥,注意重心是否要被计算),对于重心的每个儿子都递归进去做同样的操作,最后累加即可(看代码就懂了)。
题意就是求长度不超过k的路径的数量总数。
代码:
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200005;
struct A{
int v,c,next;
}e[N];
int p[N],q[N],dep[N],size[N],msize[N],vis[N],n,K,eid,l,r;
void init(){
memset(p,-1,sizeof(p));
memset(vis,0,sizeof(vis));
l=r=0;
eid=0;
}
void add(int u,int v,int c){
eid++;
e[eid].v=v;
e[eid].c=c;
e[eid].next=p[u];
p[u]=eid;
}
void dfs(int x){
q[++r]=x;
vis[x]=1;size[x]=1;msize[x]=0;
for(int i=p[x];i+1;i=e[i].next){
int v=e[i].v;
int c=e[i].c;
if(vis[v]) continue;
dep[v]=dep[x]+c;
dfs(v);
size[x]+=size[v];
msize[x]=max(msize[x],size[v]);
}
vis[x]=0;
}
bool cmp(int x,int y){
return dep[x]<dep[y];
}
int calc(int x,int y){//计算
int ans=0;
sort(q+x,q+1+y,cmp);
for(int i=x,j=y;i<j;i++){
while(j>i&&dep[q[i]]+dep[q[j]]>K) j--;
ans+=j-i;
}
return ans;
}
int work(int x){ //主题
l=1,r=0;
dfs(x);
int top=size[x],mi=msize[x];
for(int i=1;i<=r;i++){//找重心(保证log级)
int v=q[i];
if(max(msize[v],top-size[v])<mi) mi=max(msize[v],top-size[v]),x=v;
}
vis[x]=1;
int ans=0;
l=1;r=0;
for(int i=p[x];i+1;i=e[i].next){
int v=e[i].v;
int c=e[i].c;
if(vis[v]) continue;
dep[v]=c; dfs(v);
ans-=calc(l,r);//容斥
l=r+1;
}
dep[x]=0;q[++r]=x;
ans+=calc(1,r);
for(int i=p[x];i+1;i=e[i].next){
int v=e[i].v;
if(vis[v]) continue;
ans+=work(v);
}
return ans;
}
int main(){
for(;;){
init();
scanf("%d%d",&n,&K);
if(n==0&&K==0) return 0;
for(int i=1;i<n;i++){
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
add(u,v,c);
add(v,u,c);
}
printf("%d\n",work(1));
}
return 0;
}