假如说我们有 \(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)));
}
}
return 0;
}