倍增并查集学习笔记

假如说我们有 \(n\) 个元素,\(m\) 次操作。每次操作给定 \(x,y,z\),要求对于任意 \(0\le i\le z\),将 \(x+i\)\(y+i\) 合并,求最后的并查集形态。数据范围是 \(10^5\) 级别的。

我们考虑将 \(z\) 二进制拆分,那么可以将 \(z\) 分解为 \(O(\log n)\)\(2\) 的整数次幂之和,也就可以将区间划分为 \(O(\log n)\) 个长度为 \(2\) 的整数次幂的区间,分别操作。现在区间长度只有 \(O(\log n)\) 种,考虑对于每种长度维护一个并查集。具体地,假如操作是 \(x,y,2^k\),那么我们需要将第 \(k\) 个并查集中的 \(x\)\(y\) 合并,表示从 \(x\)\(y\) 开始的连续 \(2^k\) 个数对应相同。

操作完之后,我们再考虑从大至小将大区间的贡献下传到小区间。我们满足第 \(2^{k+1}\) 层下传完了过后 \(2^k\) 这层的并查集形态符合所有大于等于 \(2^k\) 的区间产生的影响。原来的形态是符合本层区间产生的影响,那么我们将 \([l,l+2^{k+1})\) 在并查集中的根 \(u\) 找出来,将 \([l,l+2^k)\)\([u,u+2^k)\)、将 \((l+2^k,l+2^{k+1})\)\((u+2^k,u+2^{k+1})\) 合并,即可满足条件。时间复杂度 \(O((n+m)\log n)\)

code
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, fa[21][N];
int Find(int t, int x) {
    if (fa[t][x] == x) return x;
    return fa[t][x] = Find(t, fa[t][x]);
}
inline void Merge(int t, int x, int y) {
    fa[t][Find(t, x)] = Find(t, y);
}
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= 20; j++) fa[j][i] = i;
    }
    for (int i = 1; i <= m; i++) {
        int l1, r1, l2, r2;
        scanf("%d %d %d %d", &l1, &r1, &l2, &r2);
        int len = r1 - l1 + 1, x = 0;
        for (int j = 20; j >= 0; j--) {
            if (x + (1 << j) <= len) {
                Merge(j, l1 + x, l2 + x);
                x += (1 << j);
            }
        }
    }
    for (int i = 20; i >= 1; i--) {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) {
            int u = Find(i, j);
            Merge(i - 1, j, u);
            Merge(i - 1, j + (1 << (i - 1)), u + (1 << (i - 1)));
        }
    }
	// 最后的 fa[0] 就是整个并查集的形态。
    return 0;
}

posted @ 2023-09-14 15:29  TulipeNoire  阅读(88)  评论(0)    收藏  举报