poj 1741 Tree 点分治

来源:互联网 发布:java io流测试题 编辑:程序博客网 时间:2024/05/19 08:23
//  poj 1741 Tree 点分治// 题目链接://      http://poj.org/problem?id=1741// 题目大意://      给定一棵N(1<=N<=10000)个结点的带权树,定义dist(u,v)为u,v两点间的最短路径长度,//  路径的长度定义为路径上所有边的权和。再给定一个K(1<=K<=1e9),如果对于不同的两个//  结点a,b,如果满足dist(a,b)<=K,则称(a,b)为合法点对。求合法点对个数。// 解题思路://      点分治.首先我们将树转化成有根树,这样点对有两种情况://  1)在同一个子树内//  2)路径经过根节点//      对于第一种情况,我们进行递归处理,可以转化成第二种情况.//      对于第二种情况.我们设belong[i] = x(x为根节点的某个儿子,i是在x为根节点的子树内)//  这样我们要求的就是//      dist[i] + dist[j] <= K(belong[i] != belong[j])(跨根节点)//  =   dist[i] + dist[j] <= K(i != j) - (dist[i] + dist[j] <= K(belong[i] == belong[j])//  这样,对于前者,我们求以root为根的节点的ans[root],对于后者,答案是root的儿子节点的ans[v]//  的和.最后结果是ans += ans[root] - (ans[v1] + ans[v2] + ... + ans[vk])(v1..vk为root的//  儿子).//      最后,就是这个root如何选择,如果每次暴力以每个节点为root,这样无疑是会超时的.树分治//  每次以树的重心为root,这样子树的点就会减小一般,所以是log(n),求出点对数也很好做,一遍快排//  然后O(n)复杂度可以算出.//      最后总结,求重心,以重心递归,以每个重心算出当前所有点到重心的距离dis,排序算出点对做累//  加再对重心的每个儿子算出点对做累减.//  感悟://      第一次接触点分治,看了09年漆子超大神的论文,以及各位大神的题解博客,才勉强弄懂,有种兴奋//  对算法的奥妙有着无穷的神往,不多说啦,加油~~~ ^ _ ^//  参考材料://      http://wenku.baidu.com/view/e087065f804d2b160b4ec0b5.html###(漆子超论文)//      http://blog.sina.com.cn/s/blog_6d5aa19a0100o73m.html(整体思路)//      http://blog.csdn.net/woshi250hua/article/details/7723400(明白为何做减法)//      http://blog.csdn.net/sdj222555/article/details/7893862(参考具体实现)#include <cstdio>#include <cstring>#include <iostream>#include <vector>#include <algorithm>using namespace std;const int MAXN = 10000 + 8;int num;int head[MAXN];int siz[MAXN];int vis[MAXN];int dis[MAXN];int root;int n;int k;int mins;int cnt;int ans = 0;struct Edge{    int to;    int w;    int next;    Edge(){    }    Edge(int to,int w,int next):to(to),w(w),next(next){    }}edges[MAXN << 1];void init(){    memset(head,-1,sizeof(head));    memset(vis,0,sizeof(vis));    num = 0;    ans = 0;}void add_edges(int u,int v,int w){    edges[num] = Edge(v,w,head[u]);    head[u] = num++;}void input(){    for (int i = 1;i < n;i ++){        int u,v,w;        scanf("%d%d%d",&u,&v,&w);        add_edges(u,v,w);        add_edges(v,u,w);    }}void dfssize(int u,int fa){ // 求size[u](以u为根节点的子树节点数目)    siz[u] = 1;    for (int i = head[u];i != -1;i = edges[i].next){        int v = edges[i].to;        if (v == fa || vis[v])            continue;        dfssize(v,u);        siz[u] += siz[v];    }}void dfsroot(int u,int fa,int tot){ // 求重心(找到除去这个节点,各子树中最大节点数最小)    int maxs = tot - siz[u];    for (int i = head[u];i != -1;i = edges[i].next){        int& v = edges[i].to;        if (v == fa || vis[v])            continue;        dfsroot(v,u,tot);        maxs = max(maxs,siz[v]);    }    if (maxs < mins){        mins = maxs;        root = u;    }}void dfsdis(int u,int fa,int d){ // 求各个点到重心的距离    dis[cnt++] = d;    for (int i = head[u];i != -1;i = edges[i].next){        int& v = edges[i].to;        if (v == fa || vis[v])            continue;        dfsdis(v,u,d + edges[i].w);    }}int cal(int u,int d){    cnt = 0;    dfsdis(u,0,d);    sort(dis,dis + cnt);    int i = 0;    int j = cnt - 1;    int res = 0;    while(i < j){ // O(n)经典算法        while(i < j && dis[i] + dis[j] > k) j--;        res += j - i;        i ++;    }    return res;}void dfs(int u){ // 以重心分治    cnt = 0;    mins = MAXN;    dfssize(u,0);    dfsroot(u,0,siz[u]);    ans += cal(root,0);    vis[root] = 1;    for (int i = head[root];i != -1;i = edges[i].next){        int& v = edges[i].to;        if (!vis[v]){            ans -= cal(v,edges[i].w);   //  这里减是很微妙的,在参考材料中的第三            dfs(v);                     //  中,有一些介绍,"比如1是根,1<-2<-3<-4,                                        //  dis(4,1) = 10,dis(3,1) = 5,k = 20,                                        //  那么dis(4,1)和dis(3,1)也合法,这显然                                        //  不符合逻辑",这句话的理解,让我有些疑惑        }                               //  我想应该这是重复计算了因为dis(3,4)会在                                        //  3为根的子树中计算一次,如果在1为根子树                                        //  中再次计算,就会重复    }}int main(){    //freopen("1.txt","r",stdin);    while(scanf("%d%d",&n,&k)!=EOF && n && k){        init();        input();        dfs(1);        printf("%d\n",ans);    }    return 0;}

1 0
原创粉丝点击