点分治
(淀粉汁)
对于静态的树上路径统计问题,点分治可以通过\(O(n\logn)\)的遍历复杂度求解。大概的思想是分通过每个节点的路径来统计。
首先我们对于树上统计问题,考虑这样一个思路:从每个节点开始,每次统计完所有通过这个节点的路径之后,把这个点从树上删除,然后递归统计所有剩下的子树。重复该步骤,直到统计完毕。
这个思路显然是正确的,因为我们统计完通过一个节点的路径之后,相当于统计了所有不同子树之间的路径。之后,我们就只剩下了子树之内的路径了。所以我们直接递归向下就行了。
然而,我们考虑这个方法的复杂度时,发现:如果我们随意选取节点,那么它的复杂度应该是\(O(n^2)\)的。举个例子,我们给一条链,每次从一头开始,那么每次就要遍历整棵树。所以考虑一个简单的减小复杂度的方法。
我们每次递归开始的时候,找一下这课子树的重心。然后以重心为节点继续统计。这样,由于每棵子树的大小不超过原树的一半,根据主定理,我们就可以把复杂度变成\(O(n\log n)\)。(这是纯遍历,不算内部统计答案的复杂度)
bool v[40010];//每个节点是否被删除
int rt,sum,cnt,dis[40010],size[40010],maxx[40010],a[40010];
//找重心
void find(int x,int fa){
size[x]=1;maxx[x]=0;
for(int i=head[x];i;i=edge[i].next){
if(!v[edge[i].v]&&edge[i].v!=fa){
find(edge[i].v,x);
size[x]+=size[edge[i].v];
maxx[x]=max(maxx[x],size[edge[i].v]);
}
}
maxx[x]=max(maxx[x],sum-size[x]);
if(maxx[x]<maxx[rt])rt=x;
}
//两个统计答案的函数 怎么写看题
//这里随便找了一个题的放上
//大概就是遍历 一直爆搜 按照你觉得能解决问题的方法爆搜
void getdis(int x,int fa){
if(a[x]<=k)dis[++dis[0]]=a[x];
for(int i=head[x];i;i=edge[i].next){
if(!v[edge[i].v]&&edge[i].v!=fa){
a[edge[i].v]=a[x]+edge[i].w;
getdis(edge[i].v,x);
}
}
}
int solve(int x,int len){
dis[0]=0;a[x]=len;cnt=0;
getdis(x,0);
sort(dis+1,dis+dis[0]+1);
for(int l=1,r=dis[0];r>=l;l++){
while(dis[l]+dis[r]>k)r--;
if(r<l)break;
cnt+=r-l;
}
return cnt;
}
//点分治部分
void div(int x){
ans+=solve(x,0);v[x]=true;
for(int i=head[x];i;i=edge[i].next){
if(!v[edge[i].v]){
ans-=solve(edge[i].v,edge[i].w);//会在递归之后重复计算所以要减掉
sum=size[edge[i].v];rt=0;
find(edge[i].v,x);div(rt);
}
}
}
//主函数内:
sum=n;maxx[rt]=n+1;
find(1,0);div(rt);
点分树(待补)
快踩