很经典的点分治入门题
论文: http://wenku.baidu.com/view/e087065f804d2b160b4ec0b5.html###
代码参考:http://blog.csdn.net/yang_7_46/article/details/9966455
其实还是有很多种姿态来搞,各个题解也都很清楚,差不多也就是那么个意思,所以只是来总结一下需要注意和难以理解的东西
1.为什么每次要找重心?
给定的树并不确定,所以树的深度直接影响时间复杂度(因为你每一次都需要去找经过这一个节点且两点之间距离<=k的,即是(i,j) d[i]+d[j]<=k)而每一次找重心,就可以确保树的深度至少缩小一半,把复杂度降为log(n)
2.为什么ans先是要加然后又要减?
#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 20009
#include<vector>
#include<algorithm>
using namespace std;
int rt,s[maxn],f[maxn],k,vis[maxn],n,d[maxn],ans,size,q[maxn];
struct node{
int v,w;
node(int x,int y):v(x),w(y){}
};
vector<node>g[maxn*4];
void get_root(int u,int fa){//得到子树的重心
s[u]=1;//初始size为1
f[u]=0;//注意初始化一开始就忘了
for(int i=0;i<g[u].size() ;i++){
int v=g[u][i].v;
if(v==fa||vis[v])continue;
get_root(v,u);
s[u]+=s[v];//加上儿子节点的size
f[u]=max(f[u],s[v]);//以此节点为根节点的最大子树节点数
}
f[u]=max(f[u],size-f[u]); //同上
if(f[u]<f[rt])rt=u;//更新重心
}
void dfs(int u,int fa){
s[u]=1;
q[++(*q)]=d[u];
for(int i=0;i<g[u].size() ;i++){
int v=g[u][i].v;
if(v==fa||vis[v])continue;
d[v]=d[u]+g[u][i].w;
dfs(v,u);
s[u]+=s[v];
}
}
int calc(int u,int deep){
d[u]=deep,*q=0;
dfs(u,0);//得出距离根的深度
sort(q+1,q+1+(*q));
int l=1,r=*q,ret=0;
while(l<r){//维护双指针 O(n)范围求解
if(q[l]+q[r]<=k)ret+=r-l++;
else r--;
}
return ret;
}
void solve(int u){
vis[u]=1;//访问标记
ans+=calc(u,0);//加上 d[i]+d[j]<=k的(i,j)对数
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v;
if(vis[v])continue;
ans-=calc(v,g[u][i].w);//减去在同一颗子树中 d[i]+d[j]<=k的(i,j)对数
f[0]=size=s[v];
get_root(v,rt=0);//找到以u为根的子树的重心
solve(rt);//求解
}
}
int main(){
while(scanf("%d%d",&n,&k)&&(n+k)){
for(int i=0;i<=n;i++)g[i].clear();
memset(vis,0,sizeof(vis));
for(int a,b,c,i=1;i<n;i++){
scanf("%d%d%d",&a,&b,&c);
g[a].push_back(node(b,c));
g[b].push_back(node(a,c));
}
f[0]=size=n;
get_root(1,rt=0);//找到全图的重心作为根节点
ans=0;
solve(rt);
printf("%d\n",ans);
}
return 0;
}