poj1741 Tree解题报告

来源:互联网 发布:斯大林大战火星人2知乎 编辑:程序博客网 时间:2024/06/06 02:53

题意:给定一棵n个结点的树和边上的权值,树上两点之间的距离为经过边的权值之和,若两点之间距离<=k,则认为是一个合法点对,问有多少个合法点对

(据说是楼教主男人八题之一...做了很久,又参考了网上各种大牛们的代码,终于A掉了,自己真是太弱了。。。)

分析:

由于点有10000个,所以直接枚举两个点是TLE的,本题要用到树的分治(可以参看漆子超大牛09年的论文)。首先我们可以将所有的点对分为两种:

1.经过根节点

2.不经过根节点

       对于经过根节点的点对我们可以这样算,先O(n)的计算出每个点到根的距离,存到dep[]数组中,若dep[i]+dep[j] <=k 就认为这是一个合法的点对,总数记为ans

但是,这样会把不经过根的点对算进去,所以,对于根的每个孩子结点,都要若如此处理算出ans1,ans2,ans3...... 然后ans-ans1-ans2-ans3-.........这样横跨根的合法点对就算出来了。

      第二种情况怎么办呢?这就是分治了,删去跟结点会形成一个森林,我们可以把森林的每棵树都如上述的进行处理。

      大概的思路就是如此,还要两个细节处理要注意:

1.在算dep[i]+dep[j] <=k时,单纯的两两枚举是会TLE的,所以可以先将dep排序 一个左指针L,一个右指针R,R指向的是最后一个满足条件dep[L]+dep[R] <=k的点,这样合法点对增加(R-L+1)个,由于L只增不减,R只减不增,可以在O(n)时间内算出.

2.树的分治,选择合适的根是关键,如果根选得不好,可能分的森林中一棵树结点特别的多,而其他的特别的少,复杂度容易将退化,每次我们要选择树的"重心"作为根来分治,什么是重心呢?简单来说是一棵树中,删去一个点x后,可以变成几棵树,使得这些树中最大的结点数尽量小,这样的一个x就是原树的重心,可以先刷poj1655 这道求重心的题

ayecsz1741Accepted768K188MSC++2624B

c++ 代码:

#include<cstdio>#include<algorithm>using namespace std;const int MAX = 10010;int list[MAX],next[2*MAX],p[MAX*2],c[MAX*2],a[MAX],f[MAX],s[MAX],flag[MAX],dep[MAX];int n,kkk,size,temproot,ans;void init(){    int x,y,z,i,tot=0;    ans = 0;    for (i = 1; i <= n; i++)    {        list[i] = 0;        flag[i] = true;    }    for (i = 1; i < n; i++)    {        scanf("%d%d%d",&x,&y,&z);        tot++;        next[tot] = list[x];        list[x] = tot;        p[tot] = y;        c[tot] = z;        tot++;        next[tot] = list[y];        list[y] = tot;        p[tot] = x;        c[tot] = z;    }}int getmax(int x,int y){    return x>y?x:y;}void findroot(int x,int pre){    int sum = 0,k;    k = list[x];    s[x] = 1;    f[x] = 0;    while (k > 0)    {        if (p[k] != pre && flag[p[k]])        {            findroot(p[k],x);            f[x] = getmax(f[x],s[p[k]]);            s[x] += s[p[k]];        }        k = next[k];    }    f[x] = getmax(f[x],size-s[x]);    if (f[x]<f[temproot]) temproot = x;}void dfs(int x,int pre){    int k;    k = list[x];    a[++a[0]] = dep[x];    while (k > 0)    {        if (p[k] != pre && flag[p[k]])        {            dep[p[k]] = dep[x]+c[k];            dfs(p[k],x);        }        k = next[k];    }}int calc(int root,int dist){    int l,r,ret;    a[0] = 0;    dep[root] = dist;    dfs(root,0);    sort(a+1,a+a[0]+1);    l = 1; r = a[0];    ret = 0;    while (l < r)    {        if (a[l]+a[r] <= kkk)        {            ret = ret + r-l;            l++;        }        else r--;    }    return ret;}void getsize(int x,int pre){    int k;    k = list[x];    size++;    while (k > 0)    {        if (p[k] != pre && flag[p[k]])        {            getsize(p[k],x);        }        k = next[k];    }}void work(int root){    int k;    ans = ans + calc(root,0);    flag[root] = false;    k = list[root];    while (k > 0)    {        if (flag[p[k]])        {            ans = ans - calc(p[k],c[k]);            size = 0;            getsize(p[k],0);            temproot = 0;            f[0] = size;            findroot(p[k],0);            work(temproot);        }        k = next[k];    }}int main(){    while (scanf("%d%d",&n,&kkk)== 2)    {        if (n == 0) break;        init();        temproot = 0;        size = f[0] = n;        findroot(1,0);        work(temproot);        printf("%d\n",ans);    }    return 0;}
PS:
最近还看到一道codechef上的题:

给定一棵n个结点的树,树上两点之间的距离为经过的边数,若两点之间距离为质数,则认为是一个合法点对,问有多少个合法点对?

其实,这题的思路也是树的分治,只是将合法条件换了,我们可以同样处理,将dep[i]保存到根距离为i的结点有多少个

x^dep[1]+x^dep[2]+x^dep[3]+...+x^dep[n]

将其和自身做一遍乘法,就可以得到合法点对的数量,乘法要用FFT来实现



原创粉丝点击