poj 1741 树的分治

/*
树的分治
题意:求树上两点间的距离<=k的点对数目;
因为n<=10000,暴搜一定会超时,所以很明显用的是树的点分治进行处理:点分治即为把树进行递归,分别对每个子树进行操作,
然后把每个子树的情况综合起来,对于这道题目,首先找到树根(即树的重心),对于该树,统计dis[i]+dis[j]<=k的数量,
将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,
分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,
那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算,避免重复)。
在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。
树的重心的算法可以线性求解。
树的重心的算法可以用一个dfs求出,但是因为这题的n是不确定的,我就写了两个dfs来求
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N  11000
#define inf 0x3fffffff
struct node{
int u,v,w,next;
}bian[N*4];
int head[N],yong,num[N],ma,minn,m,nn,vis[N];
void init() {
 yong=0;
 memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w) {
bian[yong].u=u;
bian[yong].v=v;
bian[yong].w=w;
bian[yong].next=head[u];
head[u]=yong++;
}
int Max(int v,int vv) {
return v>vv?v:vv;
}
void  dfs4(int u,int fa) {//求出n
  int i;
  nn++;
  for(i=head[u];i!=-1;i=bian[i].next) {
      int  v=bian[i].v;
    if(v!=fa&&!vis[v])
        dfs4(v,u);
  }
  return ;
}
void dfs1(int u,int fa) {//求重心
  num[u]=1;
  int i,tit=0;
  for(i=head[u];i!=-1;i=bian[i].next) {
    int v=bian[i].v;
    if(v!=fa&&!vis[v]) {
        dfs1(v,u);
        num[u]+=num[v];
        tit=Max(tit,num[v]);
    }
  }
  tit=Max(tit,nn-num[u]);
  if(tit<minn) {
    minn=tit;
    ma=u;
  }
  return ;
}
int diss[N],len;
void  dfs3(int u,int fa,int w) {//统计重心到每个点的距离
    int i;
   // printf("sa%d  %d",u,w);
    diss[len++]=w;
    for(i=head[u];i!=-1;i=bian[i].next) {
        int v=bian[i].v;
        if(v!=fa&&!vis[v])//这里注意判断条件要加上!vis[v];
            dfs3(v,u,w+bian[i].w);
    }
    return ;
}
int cmp(const void *a,const void *b) {
return *(int *)a-*(int *)b;
}
int dfs2(int u,int fa,int w) {//求出重心为u时的符合条件的个数
    len=0;
    dfs3(u,fa,w);
    qsort(diss,len,sizeof(diss[0]),cmp);
    int i,j,ans=0;
   // for(i=0;i<len;i++)
 //     printf("%d ",diss[i]);
   //     printf("\n");
    for(i=0,j=len-1;i<j;i++) {
        while(i<j&&diss[i]+diss[j]>m)
            j--;
         ans+=j-i;
    }
    return ans;
}
int dfs(int u) {
  minn=inf;
  memset(num,0,sizeof(num));
  nn=0;
  dfs4(u,-1);//求n
  dfs1(u,-1);//求重心
 // printf("u=%d cou=%d\n",u,nn);
  // printf("%d\n",ma);
  vis[ma]=1;//将重心标记走过
  int ans=dfs2(ma,-1,0);//以ma为重心的符合条件的对数
  //printf("a%d\n",ans);
  int i;
  for(i=head[ma];i!=-1;i=bian[i].next) {//这里注意从ma开始遍历子树
    int v=bian[i].v;
    if(!vis[v]) {
        ans-=dfs2(v,-1,bian[i].w);//将在一个子树上的情况减去,因为接下来会再算一遍,避免重复
        ans+=dfs(v);//计算子树,递归求和
    }
  }
  return ans;
}
int main() {
    int  i,j,k,ii,n;
    while(scanf("%d%d",&n,&m),n||m) {
            init();
       for(ii=1;ii<n;ii++) {
            scanf("%d%d%d",&i,&j,&k);
            addedge(i,j,k);
            addedge(j,i,k);
        }
        memset(vis,0,sizeof(vis));//
        k=dfs(1);
        printf("%d\n",k);
    }
return 0;}

posted @ 2014-10-01 16:39  HYDhyd  阅读(120)  评论(0编辑  收藏  举报