倍增并查集学习笔记

假如说我们有 \(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;
inline int read() {
	int val=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) val=(val<<3)+(val<<1)+c-'0',c=getchar();
	return val; 
}
int n,m,fa[21][N];
int get(int t,int x) {
    if (fa[t][x]==x) return x;
    return fa[t][x]=get(t,fa[t][x]);
}
inline void merge(int t,int x,int y) {
    fa[t][get(t,x)]=get(t,y);
    return;
}
int main() {
    n=read(),m=read();
    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=read(),r1=read(),l2=read(),r2=read();
        int len=r1-l1+1;
        int 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=get(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  阅读(29)  评论(0编辑  收藏  举报