POJ 1740 Tree(点分治)

题目分析

这道题是挑战上的一道题目,挑战上面代码写的很长并且不容易理解。此题也是楼教主男人八题之一。不过随着算法竞赛的难度逐年增加,以前的论文题现在也变成了模板题,关于点分治的问题很多,区域赛也有出过。
这道题具体方法是这样的,我们首先对树找重心,重心不理解的可以去查资料。然后根据重心对树进行分治,我们计算到树根距离小于等于K的节点,然后计算有多少对,但是我们会发现我们子树进行分治时这些点被重复计算,于是我们可以需要减去同在一个子树上的节点,只计算在不在同一个子树上的顶点对。然后一个递归算完即可。因为中间有排序时间复杂度为 O(nlog2n) ,如果还有什么不明白请看代码。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5+100;

struct Edge{
    int to, next, val;
}e[maxn<<1];

int head[maxn], tot, n, k, ans = 0; //前向星, 题目输入的值
int vis[maxn], siz[maxn], num[maxn], dep[maxn], S[maxn], root, top, tot_size;
// 中心,   子树节点  子节点数量最大值 距根节点距离
void addedge(int from, int to, int val){
    e[tot].to = to;
    e[tot].val = val;
    e[tot].next = head[from];
    head[from] = tot++;
}

void get_root(int u, int fa){  //寻找重心
    siz[u] = 1;
    num[u] = 0;
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if(vis[v] || v == fa) continue;
        get_root(v, u);
        siz[u] += siz[v];
        num[u] = max(num[u], siz[v]);
    }
    num[u] = max(num[u], tot_size-siz[u]);
    if(num[u] < num[root]) root = u;
}

void get_dep(int u, int fa){
    if(dep[u] <= k) S[top++] = dep[u];
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if(vis[v] || v == fa) continue;
        dep[v] = dep[u] + e[i].val;
        get_dep(v, u);
    }
}

int count_pair(int u, int val){
    top = 0;
    dep[u] = val;
    get_dep(u, -1);
    sort(S, S+top);
    int l = 0, r = top-1, res = 0;
    while(l < r){
        if(S[l] + S[r] <= k){
            res += r-l;
            ++l;
        }
        else --r;
    }
    return res;
}

void dfs(int u){  //分治
    vis[u] = 1; //该点被标记为重心
    ans += count_pair(u, 0); //计算所有点到该重心距离不超过K的所有值
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].to;
        if(vis[v]) continue;
        ans -= count_pair(v, e[i].val);
        root = 0;
        tot_size = siz[v];
        get_root(v, -1);
        dfs(root);
    }
}

void init(){
    memset(vis, 0, sizeof(vis));
    memset(head, -1, sizeof(head));
    root = tot = 0;
    num[0] = tot_size = n;
}

void solve(){
    init();
    int from, to, val;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &from, &to, &val);
        addedge(from, to, val);
        addedge(to, from, val);
    }
    ans = 0;
    get_root(1, -1);
    dfs(1);
    printf("%d\n", ans);
}

int main(){
    while(scanf("%d%d", &n, &k) != EOF && (n||k)) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值