Poj1741 Tree(Markdown)

Poj1741 Tree

题目原文

Time Limit: 1000MS
Memory Limit: 30000K
Total Submissions: 15559
Accepted: 5055

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

题目大意

有一棵n个节点的树,定义dist(u,v)是点u到点v之间的距离,如果dist(u,v)≤k,则称点对(u,v)是一个合法点对,求整棵树上合法点对的数量。

分析

算法一:暴力

  先用dfs在 n2 的时间复杂度内求出所有两个点之间的距离,然后枚举u、v,统计合法点对数量,需要开n*n的数组,时间复杂度 O(n2) ,空间复杂度 O(n2)

算法二:树的点分治

可以这样想:

  以1结点为根,求出所有经过这个节点和它的子树的路径,将符合条件的计入答案,再对每棵子树进行同样的操作,最终累加起来得到答案。

点分治:

  设d[i]数组表示结点i到根节点的距离。枚举点i、j,如果d[i]+d[j]≤k且i、j在不同的子树中,那么ans++。分析发现,对于每棵子树,都要先计算d数组(O(n)),再枚举i、j(O(n^2)),每个结点都要做一次的话,复杂度就是O(n^3),这显然不满足要求。时间都在枚举i、j是浪费掉了,于是考虑优化。有一种O(n)的扫描,如下:假设d数组是有序的,让i、j分别从左右端点向中间扫,明显地,如果d[i]+d[j]小于等于k,那么对于一切x

用重心减少分治次数:

  根据上述过程,假设一颗子树有x个节点,那么处理的复杂度就是O(xlogx),所以总共的复杂度就是 O[(x1+x2+x3+...+xn)log(x1+x2+x3+...+xn)] ,对于一棵深度为y的满二叉树,复杂度是 O[y2y(2y+11)] (分析略),当这棵满二叉树的节点数为n时,有 y=log[(n+1)/2] ,就算作 logn 吧,则复杂度大约是 O[n×(logn)2] ,这是非常快的。但是如果该树是一条链,则复杂度会降为 O(n2logn) ,这又是不能接受的了,一个很显然的做法就是把链每次从中间一分为二,再处理,复杂度又会成为 (n(logn)2) 了。可是这是一条链,对于更普遍的情况呢?这里使用树的重心,树的重心是去掉这个节点后使得最大的子树包含节点数最少的点,每次以子树的重心为根节点进行分治,其时间复杂度就可以稳保在 O[n(logn)2] 了。

树的重心的找法:

  树的重心的性质是所有点到树的重心的距离和最小,我们可以先算出任意一个点x到所有节点的距离和,假设是sumx,对于其儿子y,设w为边(x,y)的权值,totnode是整棵树的结点数, cnt[i] 是以i为根的子树的结点个数,则有 sumy=sumxwcnt[y]+w(totnodecnt[y]) ,转移的复杂度是O(1)的,利用这一点从而我们可以在O(n)的时间复杂度内求出树的重心。
  一口气写了不少。。。我之所以写的这么详细是因为在这之前我对树的分治一窍不通,我所写的都是我从这道题中学到的。

以下是代码:

//poj1741 Tree 树的点分治
#include <cstdio>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define maxn 10005
#define maxm 20005
using namespace std;
int n, k, d[maxn], q[maxn], len, cnt[maxn], ans, head[maxn], next[maxm],
    w[maxm], to[maxm], tot, G, minsum, flag, tot_node;
void add(int a, int b, int c)
{to[++tot]=b;w[tot]=c;next[tot]=head[a];head[a]=tot;}
int count()
{
    int i, j, s=0;
    sort(q+1,q+len+1);
    for(i=1,j=len;i<j;i++)
    {while(i<j && q[i]+q[j]>k)j--;s+=j-i;}
    return s;
}

void dfs(const int x, const int f, const int dist)
{
    int p; 
    if(flag==1){q[++len]=d[x];minsum+=dist;tot_node++;}
    if(flag==2)q[++len]=dist;
    d[x]=dist;cnt[x]=1;
    for(p=head[x];p;p=next[p])
    {
        if(to[p]==f)continue;
        dfs(to[p],x,dist+w[p]);
        cnt[x]+=cnt[to[p]];
    }
}
void findG(const int x, const int f, const int sumx)
{
    int p, t;
    for(p=head[x];p;p=next[p])
    {
        if(to[p]==f)continue;
        t=sumx-w[p]*cnt[to[p]]+(tot_node-cnt[to[p]])*w[p];
        if(t<minsum)minsum=t,G=to[p];
        findG(to[p],x,t);
    }
}
void deal(const int root, const int f)
{
    int p, g;
    if(to[head[root]]==f)head[root]=next[head[root]];
    else for(p=head[root];p;p=next[p])
        if(to[next[p]]==f){next[p]=next[next[p]];break;}
    len=minsum=0;
    flag=1;
    tot_node=0;
    dfs(root,f,0);
    ans-=count();
    G=root;
    findG(root,inf,minsum); 
    flag=2;
    len=0;
    dfs(G,inf,0);
    ans+=count();
    g=G;
    for(p=head[g];p;p=next[p])
        deal(to[p],g);
}
int main()
{
    int a, b, c, i;
    while(scanf("%d%d",&n,&k),n)
    {
        tot=len=ans=0;
        memset(head,0,sizeof(head));
        memset(next,0,sizeof(next));
        memset(d,0x3f,sizeof(d));
        for(i=1;i<n;i++)
        {scanf("%d%d%d",&a,&b,&c);add(a,b,c);add(b,a,c);}
        ans=0;
        deal(1,inf);
        printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(Poj1741 Tree(Markdown))