返回顶部

AcWing - 252 - 树 = 点分治

https://www.acwing.com/problem/content/description/254/

这个才是真的点分治,上次那个直接树dp就结束了,因为和子树的size没有什么关系,甚至那个重心是自己骗自己的。

这个点分就厉害了,每次都要找新的重心。

因为每次分治合并答案的代价是和当前子树的size有关的,所以才要重新找子树的重心分治。

需要用容斥减去答案的原因是因为他的计算方法。

抄别人代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 40000 + 5;

int n, m, k, tot, all_node, ans, root;
// root 为重心
int head[MAXN], max_part[MAXN], siz[MAXN], len[MAXN], d[MAXN];
// d[i] 表示i到根节点的距离
// len[i] 表示找到的从某个根节点出发的第i条路径长度,len[0]表示路径的数量
// max_part[i] 表示i的最大的子树大小
bool vis[MAXN];
// vis用于求重心时避免重复访问

struct Edge {
    int next, to, dis;
} edge[MAXN << 1];
// edge 用于存边,记得给反向边留位置

inline void add(int from, int to, int dis) {
    edge[++tot].to = to;
    edge[tot].dis = dis;
    edge[tot].next = head[from];
    head[from] = tot;
}

void get_root(int u, int fa) { // 找到树的重心
    max_part[u] = 0, siz[u] = 1;
    for(int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if(v == fa || vis[v])
            //既不要往回走,也不能越过上一次分治已经删除(vis)的点
            continue;
        get_root(v, u);
        siz[u] += siz[v];
        max_part[u] = max(max_part[u], siz[v]);
    }
    max_part[u] = max(max_part[u], all_node - siz[u]);
    if(max_part[u] < max_part[root])
        root = u;
}

void get_dis(int u, int fa) { // 求出每个点到根节点的距离
    len[++len[0]] = d[u];
    for(int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if(v == fa || vis[v])
            //既不要往回走,也不能越过上一次分治已经删除(vis)的点
            continue;
        d[v] = d[u] + edge[i].dis;
        get_dis(v, u);
    }
}

int cal(int u, int now) { // 计算以u为根的所有情况的答案,now是已经累计的路径长度
    d[u] = now, len[0] = 0;
    get_dis(u, 0);
    sort(len + 1, len + len[0] + 1);
    int all = 0;
    for(int l = 1, r = len[0]; l < r;) {
        //sort之后,和<=k的必定是中间的连续一段
        if(len[l] + len[r] <= k) {
            all += r - l;
            ++l;
        } else
            r--;
    }
    return all;
}

void solve(int u) { // 求解以u为重心的情况
    vis[u] = true;
    ans += cal(u, 0);
    for(int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if(vis[v])
            continue;
        ans -= cal(v, edge[i].dis); // 减去不合法的
        // 为什么?这里传进去的是啥?
        all_node = siz[v];
        root = 0;
        get_root(v, u);
        solve(root);
    }
}

int main() {
    while(1) {
        scanf("%d%d", &n, &k);
        if(!n && !k)
            break;

        tot = 0, ans = 0;
        memset(head, -1, sizeof(head));
        memset(max_part, 0, sizeof(max_part));
        memset(siz, 0, sizeof(siz));
        memset(vis, false, sizeof(vis));

        for(int i = 1; i < n; ++i) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            //把题目的坐标偏移到从1开始
            add(u + 1, v + 1, w);
            add(v + 1, u + 1, w);
        }

        all_node = n, max_part[0] = n, root = 0;
        get_root(1, -1);
        //开始分治
        solve(root);
        printf("%d\n", ans);
    }
    return 0;
}

作者:MILLOPE
链接:https://www.acwing.com/solution/AcWing/content/2826/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2019-10-09 16:41  Inko  阅读(...)  评论(...编辑  收藏