点分治复习笔记

本文深入探讨了点分治算法,一种处理树上路径问题的有效方法。通过分治递归和重心选择,确保递归层数为logn,降低复杂度至O(n(logn)^2)。介绍了点分治在路径长度相关问题中的应用,如luoguP4149,详细解释了三种处理LCA不是固定点情况的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前的记录:

点分治可以处理一些普通的 tree dp 或者是树剖无法处理的树上路径问题。

简单说就是在一棵树上做分治,考虑对于一个固定根的树,路径只有可能经过根节点,或者完全在它的子树中。后者可以分治递归下去解决。为了保证递归层数是logn 的。我们每次选子树树的重心作为子树的新根。这样节点总数就减少了一半。

我们一般都会考虑无限制的经过根节点的情况(不考虑有一个更深的LCA),减去它完全在它子树中的情况。这种做法类似容斥原理的思路。无限制的经过根节点的情况具体问题具体分析,本题只需要考虑子树内的点对就好。对于一个树,O(N)找到每个子孙节点到rt的距离。放到一个数组里面,排个序,双指针扫描一下就好了。复杂度O(n(logn)^2)

注意在求子树大小siz的时候不要在外面memset。这会非常的慢,达到n^2.在里面令siz[x]一开始为1就好了。

luoguP4178

luoguP4149
给定一棵树,问长度为k的路径的最小边数。

看到了路径与长度有关,套一个点分治。假设当前固定了一个点,为了处理LCA不是它的情况,有三种方法。

1:对于操作可以抵消的情况,先统计 x x x这个根,只考虑它的子树的一些点的贡献。然后减去它孩子为根的答案。再递归到它的儿子。

2:如果操作不可以抵消,可以给每个子树染色。只统计颜色不一样的答案。

3:如果染色也不方便,可以按顺序处理子树。用什么东西把前面子树的信息保存下来,然后这个直接查就行了。 注意处理完一个以它为根的,要把这一轮保存的值清空。

这一题采用第三种方法,用桶来存值。枚举每个子树,先查再插。O(nlogn)

#include <bits/stdc++.h>// id from 0
using namespace std;//bar就是那个桶
const int N = 2e5 + 5, K = 1e6 + 5, INF = 0x3f3f3f3f;
int n, k, cnt, las;
int head[N], nxt[N<<1], v[N<<1], w[N<<1], tot;
int used[N], siz[N], dis1[N], dis2[N], bar[K];
int rt, Minsiz, all, ans = INF;
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
inline void add(int x, int y, int z) {
    v[++tot] = y;
    w[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
}
inline void getrt(int x, int fa) {
    siz[x] = 1;
    int mx = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        if (v[i] != fa && !used[v[i]]) {
            getrt(v[i], x);
            siz[x] += siz[v[i]];
            mx = max(mx, siz[v[i]]);
        }
    }
    mx = max(mx, all - siz[x]);
    if (mx < Minsiz) {
        Minsiz = mx;
        rt = x;
    }
}
inline void getdis(int x, int fa, int w1, int w2) {
    if (w1 > k) return ;
    dis1[++cnt] = w1; dis2[cnt] = w2;
    for (int i = head[x]; i; i = nxt[i]) {
        if (used[v[i]] || v[i] == fa) continue;
        getdis(v[i], x, w1 + w[i], w2 + 1);
    }
}

inline void calc(int x) {
    bar[0] = 0; cnt = 0;
    for (int i = head[x]; i; i = nxt[i]) {
        if (used[v[i]]) continue;
        las = cnt;
        getdis(v[i], x, w[i], 1);
        for (int j = las + 1; j <= cnt; ++j) ans = min(ans, bar[k - dis1[j]] + dis2[j]);
        for (int j = las + 1; j <= cnt; ++j) bar[dis1[j]] = min(bar[dis1[j]], dis2[j]);
    }
    for (int i = 1; i <= cnt; ++i) bar[dis1[i]] = INF;
}
inline void solve(int x) {
    used[x] = 1; calc(x); //printf("x = %d\n", x);
    for (int i = head[x]; i; i = nxt[i]) {
        if (!used[v[i]]) {
            all = siz[v[i]]; Minsiz = INF;
            getrt(v[i], x); solve(rt);
        }
    }
}
int main() {
    //freopen("luoguP4149.in", "r", stdin);
    //freopen("try.out", "w", stdout);
    n = read(); k = read();
    for (int i = 1; i < n; ++i) {
        int x = read(), y = read(), z = read();
        add(x, y, z); add(y, x, z);
    }
    memset(bar, 0x3f, sizeof bar);
    all = n;
    Minsiz = INF; getrt(0, 0);
    solve(rt);
    if (ans == INF) puts("-1");
    else printf("%d\n", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值