poj 1741(1987) 树分治(求距离小于某值的点对数)

题意:给定一棵带权无向树,并给定一个整数m。求树上点间最短路不大于m的点对数量。(1987题意完全相同,又重新写一遍熟练一下)

思路:比较明显的思路是用LCA来做,但问题是用LCA的话,当前问题的查询数量级为n^2,所以不管使用离线LCA还是在线LCA都逃不过O(n^2)的复杂度。经过学习知道了树分治这种东西。

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。
在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。
树的重心的算法可以线性求解,关于树的重心的定义和解法可以参见poj1655。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define clr(s,t) memset(s,t,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 10005
struct edge{
    int y,w,next;
}e[N<<1];
int n,m,top,res,root,len,size;
int first[N],used[N],son[N],rnum[N],dis[N],q[N];
void init(){
    top = res = root = 0;
    clr(first, -1);
    clr(used, 0);
    clr(son, 0);
    clr(rnum, 0);
    rnum[0] = N;
}
int cmp(const void* a,const void* b){
    return (*(int*)a) - (*(int*)b);
}
void add(int x,int y,int w){
    e[top].y = y;
    e[top].w = w;
    e[top].next  = first[x];
    first[x] = top++;
}
void findroot(int x,int fa){
    int i,y;
    son[x] = 1;
    rnum[x] = 0;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        if(y!=fa && !used[y]){
            findroot(y, x);
            son[x] += son[y];
            rnum[x] = max(rnum[x], son[y]);
        }
    }
    rnum[x] = max(rnum[x], size-son[x]);
    if (rnum[x] < rnum[root])
        root = x;
}
void getdis(int x,int fa){
    int i;
    q[len++] = dis[x];
    son[x] = 1;
    for(i = first[x];i!=-1;i=e[i].next)
        if(e[i].y != fa &&!used[e[i].y]){
            dis[e[i].y] = dis[x]+e[i].w;
            getdis(e[i].y, x);
            son[x] += son[e[i].y];
        }
}
int update(int len){
    int i,j,sum=0;
    qsort(q, len, sizeof(int), cmp);
    for(i = 0,j = len-1;;i++){
        while(q[i]+q[j]>m)
            j--;
        if(i>=j)
            break;
        sum += j-i;
    }
    return sum;
}
void tdc(int x){//tree_divide_conquer
    int i,y;
    findroot(x,root=0);
    len = 0;
    dis[root] = 0;
    getdis(root,0);
    res += update(len);
    used[root] = 1;
    for(i = first[root];i!=-1;i=e[i].next)
        if(!used[y=e[i].y]){
            len = 0;
            dis[y] = e[i].w;
            getdis(y,root);
            res -= update(len);
            size = son[y];
            tdc(y);
        }
}
int main(){
    while(scanf("%d %d",&n,&m) && (n+m)){
        int i,a,b,w;
        init();
        for(i = 1;i<n;i++){
            scanf("%d %d %d",&a,&b,&w);
            add(a,b,w);add(b,a,w);
        }
        size = n;
        tdc(1);
        printf("%d\n",res);
    }
    return 0;
}

1987:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define clr(s,t) memset(s,t,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 40005
struct edge{
    int y,w,next;
}e[N<<1];
int first[N],used[N],son[N],tnum[N],q[N],dis[N];
int top,n,m,th,res,len,size,root;
void init(){
    top = res = len = 0;
    clr(first, -1);
    clr(used, 0);
    tnum[0] = N;
}
void add(int x,int y,int w){
    e[top].y = y;
    e[top].w = w;
    e[top].next = first[x];
    first[x] = top++;
}
int cmp(const void* a,const void* b){
    return (*(int*)a) - (*(int*)b);
}
void getroot(int x,int fa){
    int i,y;
    son[x] = 1;
    tnum[x] = 0;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        if(!used[y] && y!=fa){
            getroot(y,x);
            son[x] += son[y];
            tnum[x] = max(tnum[x], son[y]);
        }
    }
    tnum[x] = max(tnum[x], size-son[x]);
    if(tnum[x] < tnum[root])
        root = x;
}
void getdis(int x,int fa){
    int i,y;
    q[len++] = dis[x];
    son[x] = 1;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        if(!used[y] && y!=fa){
            dis[y] = dis[x]+e[i].w;
            getdis(y,x);
            son[x] += son[y];
        }
    }
}
int updatedis(){
    int i,j,sum = 0;
    qsort(q, len, sizeof(int), cmp);
    for(i = 0,j = len-1;;i++){
        while(q[i]+q[j] > th)
            j--;
        if(i>=j)
            break;
        sum += j-i;
    }
    return sum;
}
void tdc(int x){
    int i,y;
    getroot(x,root = 0);
    dis[root] = len = 0;
    getdis(root,0);
    res += updatedis();
    used[root] = 1;
    for(i = first[root];i!=-1;i=e[i].next){
        y = e[i].y;
        if(!used[y]){
            dis[y] = e[i].w;
            len = 0;
            getdis(y, root);
            res -= updatedis();
            size = son[y];
            tdc(y);
        }
    }
}
int main(){
    int i,a,b,c;
    char ch;
    init();
    scanf("%d %d",&n,&m);
    for(i = 0;i<m;i++){
        scanf("%d %d %d %c",&a,&b,&c,&ch);
        add(a,b,c);
        add(b,a,c);
    }
    scanf("%d",&th);
    size = n;
    tdc(1);
    printf("%d\n",res);
    return 0;
}


你可能感兴趣的:(poj 1741(1987) 树分治(求距离小于某值的点对数))