点分治 复习笔记

之前diaoye的一道题是要用点分治写..但是我省选前临时学的点分治,当时又只打了几个板子,中间又没有写过有关的题,于是现在就似乎不太会了,刚好昨天又讲了,今天就复习一下

引用讲课 PPT P P T 里的一段话

点分治,是处理树上路径/连通块的一种常见算法。
这一类问题有时可以通过lca来处理,但是有的时候复杂度会略高。
点分治,每次对当前的树求出重心,处理所有经过重心的路径/连通块,然后递归每个子树。
这样,每个节点只会出现在log次分治中。

然后点分治我觉得可能也是跟容斥有关的,因为它主要思想还是先计算不考虑限制的答案再减去包含限制的答案,然后比较重要的地方就是每次都以树的重心为根来进行分治处理。

update:看来自己还是对这个算法理解不够… 感谢 ylsoijulao y l s o i j u l a o 的讲解
就是有一些细节地方还是需要注意 第一个就是每次分治对于每棵子树又要重新确定重心,这我原来是没有做的…重新确定重心后那么子树的 size s i z e 又会改变, 所以每次处理一棵子树的时候又要重新记一次 size s i z e

直接上例题吧
https://www.luogu.org/problemnew/show/P4178

luogu4178 Tree

裸题,直接上板子即可

#include<bits/stdc++.h>
#define For(i, a, b) for(register int i = a; i <= b; ++ i)
#define go(x, i) for(register int i = head[x]; i; i = nxt[i])

using namespace std;

const int maxn = 40000 + 10;
int to[maxn << 1], head[maxn], nxt[maxn << 1], v[maxn << 1], e;
int all, n, root, size[maxn], sizemax[maxn], ans;
int now[maxn], dep[maxn], k, vis[maxn];

void add(int x, int y, int z) {
    to[++ e] = y;
    nxt[e] = head[x];
    head[x] = e;
    v[e] = z;
}

void get_dep(int x, int fa) {
    now[++ now[0]] = dep[x];
    go(x, i) 
        if(!vis[to[i]] && to[i] != fa) {
            dep[to[i]] = dep[x] + v[i];
            get_dep(to[i], x);
        }
}

int cal(int x, int From) {
    now[0] = 0, dep[x] = From;
    get_dep(x, 0); sort(now + 1, now + now[0] + 1);
    int l = 1, r = now[0], res = 0;
    while(l < r) 
        if(now[l] + now[r] <= k)
            res += r - l, ++ l;
        else
            -- r;
    return res;
}

void get_root(int x, int fa) {
    size[x] = 1, sizemax[x] = INT_MIN;
    go(x, i) 
        if(!vis[to[i]] && to[i] != fa) {
            get_root(to[i], x);
            size[x] += size[to[i]];
            if(size[to[i]] > sizemax[x])
                sizemax[x] = size[to[i]];
        }
    if(all - size[x] > sizemax[x])
        sizemax[x] = all - size[x];
    if(sizemax[root] > sizemax[x]) 
        root = x;
}

void solve(int x) {
    vis[x] = 1, get_root(x, 0); 
    ans += cal(x, 0);
    go(x, i) 
        if(!vis[to[i]]) {
            ans -= cal(to[i], v[i]);
            all = size[x], root = 0;
            get_root(to[i], x);
            solve(root);
        }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("4178.in", "r", stdin);
    freopen("4178.out", "w", stdout);
#endif
    int x, y, z;
    scanf("%d", &n);
    For(i, 1, n - 1) {
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
    }
    all = n, sizemax[0] = INT_MAX;
    get_root(1, 0), scanf("%d", &k);
    solve(root); printf("%d\n", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值