[POJ 1741]Tree[树分治]

题目链接: [POJ 1741]Tree[树分治]

题意分析:

统计树上一共有多少对结点,两者之间的最短距离不超过k。

解题思路:

1e4个点,暴搜肯定是不行的。这里我们采用树分治来解决这个问题。

大体思路:将树不断地划分成两块——>以划分点作为“中间点”(我们称为重心),统计该点周围点到该点的距离ds[],统计ds[]+ds[]小于等于k的对数——>回归上层继续计算

总结来说就是:划分——>计算。类比下归并排序即可。

根据思路就有了两个问题:1.重心怎么找?2.只统计两侧点会不会丢失情况?

1.规定重心:删去该点后最大子图顶点数最少的结点。根据定义递归查找即可。

2.设S点为重心,划分出了两个块,那么总共有三种情况:1)点对在同一个连通块;2)点对在不同的连通块;3)点对之中有一个为S点;

对于3)情况,我们引入一个虚拟点,到S的距离为0,情况转化为2)。对于情况1),我们在划分子树的过程中,又变成了情况2)或3),所以只要周围点互相枚举即可。由于我们的统计方法是把ds排序后,进行比对,就有可能枚举了两个在同一个块的点,所以要预先剪掉。

个人感受:

一个晚上+凌晨的时间,终于把它搞懂了= =。说白了就是归并的分而治之,然而看得着实不易啊TAT

具体代码如下:

#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<string>
#define ll long long
#define pii pair<int, int>
#define pr(x) cout << #x << " = " << (x) << '\n';
using namespace std;

const int INF = 0x7f7f7f7f;
const int MAXN = 1e4 + 111;

struct Edge {
    int to, next, w;
}edge[2 * MAXN];
int head[MAXN], tot;

int n, k, ans;
int subSize[MAXN];
bool vis[MAXN];

void init() {
    ans = tot = 0;
    memset(vis, 0, sizeof vis);
    memset(head, -1, sizeof head);
}

void add_edge(int u, int v, int w) {
    edge[tot].to = v;
    edge[tot].w = w;
    edge[tot].next = head[u];
    head[u] = tot++;
}

int compute_subSize(int u, int p) {
    int c = 1;
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v != p && !vis[v]) c += compute_subSize(v, u);
    }
    return subSize[u] = c;
}

// 寻找删除该点后最大子树的顶点数最少的点
pii search_it(int u, int p, int t) {
    pii ret = make_pair(INF, -1);
    int s = 1, m = 0;
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v != p && !vis[v]) {
            ret = min(ret, search_it(v, u, t));
            m = max(m, subSize[v]);
            s += subSize[v];
        }
    }
    m = max(m, t - s); // t - s:包含u点的子树顶点个数
    return min(ret, make_pair(m, u));
}

void getdis(int u, int p, int d, vector<int> &ds) {
    ds.push_back(d);
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v != p && !vis[v]) getdis(v, u, d + edge[i].w, ds);
    }
}

int countAns(vector<int> &ds) {
    int ret = 0;
    sort(ds.begin(), ds.end());
    int i = 0, j = ds.size() - 1;
    while (i < j) {
        while (ds[i] + ds[j] > k && i < j) --j;
        ret += j - i;
        ++i;
    }
    return ret;
}

void solve(int v) {
    // 找重心
    compute_subSize(v, -1);
    int s = search_it(v, -1, subSize[v]).second;
    vis[s] = 1;

    // 继续划分
    for (int i = head[s]; ~i; i = edge[i].next) {
        if (!vis[edge[i].to]) solve(edge[i].to);
    }

    // 计算个数
    vector<int> ds;
    ds.push_back(0); // 距离s为0的虚拟点
    for (int i = head[s]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (vis[v]) continue;

        vector<int> tds;
        getdis(v, s, edge[i].w, tds);

        ans -= countAns(tds);
        ds.insert(ds.end(), tds.begin(), tds.end());
    }

    ans += countAns(ds);
    vis[s] = 0; // 复原,为大子树计算服务2333
}

int main()
{
    #ifdef LOCAL
    freopen("C:\\Users\\apple\\Desktop\\in.txt", "r", stdin);
    #endif
    while (~scanf("%d%d", &n, &k) && (n | k)) {
        init();
        int u, v, l;
        for (int i = 1; i < n; ++i) {
            scanf("%d%d%d", &u, &v, &l);
            add_edge(u, v, l);
            add_edge(v, u, l);
        }
        solve(1);
        printf("%d\n", ans);
    }
    return 0;
}


你可能感兴趣的:(树分治)