很经典的点分治入门题
论文: 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先是要加然后又要减?
在计算以4为根和以3为根的时候1,2被重复计算
#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; }