点分治、点分树的理解

点分治

题目类型

告诉我们一棵无根树,求解树上距离关系等 ( ( (如有多少条长度不超过K的路径 ) ) ),如果直接求解可能时间复杂度会很大,我们可以通过换的重心的方式来实现降低时间复杂度。

思路

我们可以通过更换重心的方法,对不同重心进行寻找与重心相连的子树内的所有路径,分别求解相关内容即可,因为通过更换重心,我们每次可以将子树个数变化为 ≤ s i z e / 2 \le size/2 size/2,其中 s i z e size size为子树的大小,这样我们只要便利 l o g 2 n log_2n log2n个子树即可,这样时间复杂度可以降到 n l o g 2 n nlog_2n nlog2n

实现

洛谷3806
一道很经典的点分治题目
每次寻找重心,将树划分成 l o g 2 n log_2n log2n段,然后我们用一个数组存一下距离,然后求出每个子树到重心的所有距离,然后分三种情况讨论
1. 1. 1.两个点在同一个子树内 – 递归求解
2. 2. 2.两个点其中一个点是重心 – 判断两点距离是否为 m m m
3. 3. 3.两个点在不同子树内 – 存一个数组记录距离,然后查找 m − m- m一个点到重心的距离,再判断这个距离再其他子树是否存在

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 10100, M = N * 2, S = 10010000;

int n, m, Q;
int h[N], ne[M], w[M], e[M], idx;
void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool fl;
bool st[N];
int f[S], q[N], p[N];

int get_size(int u, int fa){
    if(st[u]) return 0;
    int res = 1;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        res += get_size(j, u);
    }
    return res;
}

int get_wc(int u, int fa, int tot, int &wc){
    if(st[u]) return 0;
    int sum = 1, ms = 0;
    for(int i = h[u]; ~ i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        int t = get_wc(j, u, tot, wc);
        ms = max(ms, t);
        sum += t;
    }

    ms = max(ms, tot - sum);
    if(ms <= tot / 2) wc = u;
    return sum;
}

void get_dist(int u, int fa, int dist, int &qt){
    if(st[u] || dist > m) return;
    q[qt ++] = dist;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        get_dist(j, u, dist + w[i], qt);
    }

}

void calc(int u){
    if(st[u] || fl) return;
    get_wc(u, -1, get_size(u, -1), u);
    int pt = 0; 
    st[u] = 1;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i], qt = 0;

        get_dist(j, u, w[i], qt);
        for(int k = 0; k < qt; k ++){
            if(q[k] == m){
                fl = 1;
                for(int t = 0; t < pt; t ++) f[p[t]] = 0;
                return;
            }
            if(f[m - q[k]]){
                fl = 1;
                for(int t = 0; t < pt; t ++) f[p[t]] = 0;
                return;
            }
            p[pt ++] = q[k];
        }
        for(int k = 0; k < qt; k ++) f[q[k]] ++;
    }

    for(int i = 0; i < pt; i ++) f[p[i]] = 0;

    for(int i = h[u]; ~i; i = ne[i]) calc(e[i]);
}

int main(){

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> Q;

    memset(h, -1, sizeof h);

    for(int i = 1; i < n; i ++){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    while(Q --){
        cin >> m;
        fl = 0;
        memset(st, 0, sizeof st);
        calc(1);
        if(fl) cout << "AYE\n";
        else cout << "NAY\n";
    }


    return 0;
}

点分树

题目类型

告诉我们一棵无根树,求解树上距离关系等 ( ( (如有多少条长度不超过K的路径 ) ) ),有多组询问,询问次数很多。

思路

与点分治思路类似,但是因为多次查询,我们不能每次都去点分治。我们会发现一棵树的重心基本不会改变,这样我们就可以先预处理出来所有的重心,然后保留重心和父亲以及儿子之间的关系,就可以直接查找,效率会变高

实现

洛谷3241
这个就需要我们预处理重心,然后直接查找重心父亲和儿子即可,因为每个点度数很小,所以我们可以用 v e c t o r vector vector将这些内容存下来,求解然后对于所有距离和,答案为所有满足条件的点p到u的距离+p到u的距离×g的不包含u的子联通块siz和+g的不包含u的子联通块中的点到u的距离之和,我们利用前缀和+二分的技巧,可以快速求出满足的个数,求解即可

#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int N = 150100, M = N * 2;

int n, Q, A;
int age[N];
int h[N], ne[M], e[M], idx, w[M];
bool st[N];

struct Father{
    int u, num;
    LL dist;
};

vector<Father> f[N];

struct Son{
    int age;
    LL dist;
    bool operator< (const Son &W)const{
        return age < W.age;
    } 
};

vector<Son> son[N][3];

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int get_size(int u, int fa){
    if(st[u]) return 0;
    int res = 1;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        res += get_size(j, u);
    }
    return res;
}

int get_wc(int u, int fa, int tot, int &wc){
    if(st[u]) return 0;
    int sum = 1, ms = 0;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        int t = get_wc(j, u, tot, wc);
        sum += t;
        ms = max(ms, t);
    }

    ms = max(ms, tot - sum);
    if(ms <= tot / 2) wc = u;
    return sum;
}

void get_dist(int u, int fa, int dist, int wc, int k, vector<Son> &p){
    if(st[u]) return;
    f[u].push_back({wc, k, dist});
    p.push_back({age[u], dist});
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa) continue;
        get_dist(j, u, dist + w[i], wc, k, p);
    }
}

void calc(int u){
    if(st[u]) return;
    get_wc(u, -1, get_size(u, -1), u);
    st[u] = 1;

    for(int i = h[u], k = 0; ~i; i = ne[i]){
        int j = e[i];
        if(st[j]) continue;
        auto &p = son[u][k];
        get_dist(j, -1, w[i], u, k, p);
        k ++;
        p.push_back({-1, 0}), p.push_back({A+1, 0});
        sort(p.begin(), p.end());
        for(int t = 0; t < p.size(); t ++) p[t].dist += p[t - 1].dist;
    }

    for(int i = h[u]; ~ i; i = ne[i]) calc(e[i]);
}

LL query(int u, int l, int r){
    LL res = 0;

    for(auto &t:f[u]){
        int g = age[t.u];
        if(g >= l && g <= r) res += t.dist;
        for(int i = 0; i < 3; i ++){
            if(i == t.num) continue;
            auto &p = son[t.u][i];
            if(p.empty()) continue;
            int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
            int b = lower_bound(p.begin(), p.end(), Son({r+1, -1})) - p.begin();
            res += t.dist * (b - a) + p[b - 1].dist - p[a - 1].dist;
        }
    }

    for(int i = 0; i < 3; i ++){
        auto &p = son[u][i];
        if(p.empty()) continue;
        int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
        int b = lower_bound(p.begin(), p.end(), Son({r+1, -1})) - p.begin();
        res += p[b-1].dist - p[a-1].dist;
    }

    return res;
}

int main(){

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> Q >> A;

    for(int i = 1; i <= n; i ++) cin >> age[i];

    memset(h, -1, sizeof h);

    for(int i = 1; i < n; i ++){
        int a, b, c; 
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    calc(1);
    LL ans = 0;
    while(Q --){
        int u, a, b;
        cin >> u >> a >> b;
        int l = (ans + a) % A, r = (ans + b) % A;
        if(l > r) swap(l, r);
        ans = query(u, l, r);
        cout << ans << "\n";
    }

    return 0;
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值