poj1741 Tree解题报告

题意:给定一棵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来实现



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值