[POJ 1741] Tree (点分治)

链接

POJ 1741


题意

给出一棵树,n个节点(n <= 10000),边含权,问有多少对点对之间的距离不超过k。


题解

楼教主男人八题之一,貌似是点分治的入门题。

这里大致介绍下点分治的过程:
实际上有个很早就接触过的分治算法叫“归并排序”,貌似是从那时候我才对分分治和nlogn算法有一些体会,归并排序的每层都是O(n),重点在于层数很少,只有logn层,因为每次都将区间分成原来的一半。
树上的点分治其实也差不多,重点在于怎么将树形结构分成尽可能相等的两部分,这就要求找到树的重心,然后向下递归即可,基本上就是树上的归并。
树的重心事实上不一定能将树分成尽可能相等的两部分(比如一个有三条相等的链的树),但是这样并不会增加时间复杂度(相当于logk(n)对不同k的杂糅)。
最后就是分治算法的应用场景,应该需要多做点题好好体会体会。


代码
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn (10010)
struct node
{
    int v, l;
    node() { }
    node(int _v, int _l) : v(_v), l(_l) { }
};
vector<node> g[maxn];
int n, size, s[maxn], f[maxn], root, d[maxn], K, ans, done[maxn];
vector<int> dep;
void getroot(int u, int fa)
{
    s[u] = 1, f[u] = 0;
    for(int i = 0, v; i < g[u].size(); i++)
    if((v = g[u][i].v) != fa && !done[v]) {
        getroot(v, u);
        s[u] += s[v];
        f[u] = max(f[u], s[v]);
    }
    f[u] = max(f[u], size - s[u]);
    if(f[u] < f[root]) root = u;
}
void getdep(int u, int fa)
{
    s[u] = 1;
    dep.push_back(d[u]);
    for(int i = 0, v; i < g[u].size(); i++)
    if((v = g[u][i].v) != fa && !done[v]) {
        d[v] = d[u] + g[u][i].l;
        getdep(v, u);
        s[u] += s[v];
    }
}
int calc(int u, int init)
{
    dep.clear(); d[u] = init;
    getdep(u, 0);
    sort(dep.begin(), dep.end());
    int ret = 0;
    for(int l = 0, r = dep.size() - 1; l < r; )
    {
        if(dep[l] + dep[r] <= K) ret += r - l++;
        else r--;
    }
    return ret;
}
void work(int u)
{
    ans += calc(u, 0);
    done[u] = 1;
    for(int i = 0, v; i < g[u].size(); i++)
    if(!done[v = g[u][i].v]) {
        ans -= calc(v, g[u][i].l);
        f[0] = size = s[u];
        getroot(v, root = 0);
        work(root);
    }
}
int main()
{
    while(cin >> n >> K)
    {
        if(!n && !K) break;
        for(int i = 0; i <= n; i++)
        { g[i].clear(); done[i] = 0; }
        for(int i = 0, u, v, l; i < n-1; i++)
        {
            scanf("%d%d%d", &u, &v, &l);
            g[u].push_back(node(v, l));
            g[v].push_back(node(u, l));
        }
        f[0] = size = n;
        getroot(1, root = 0);
        ans = 0;
        work(root);
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值