题解 CF566D【Restructuring Company】

区间合并的并查集。

区间合并操作显然不能硬上,考虑如何优化这一段的合并。

例如,假设目前并查集长这样(如下图),此时希望合并 \(2\sim 7\),你会怎么做呢?

相信我们的第一反应都是:直接把左侧三个集合合并就好了,而不是去合并 \((2,3)(3,4)\cdots(6,7)\)

这确实就是我们的做法,那么我们就需要知道每个集合管辖的范围是哪段区间,或者说需要知道在这一集合后方不属于这一集合的第一个点是多少。我们维护一个数组 \({nxt}_i\) 表示以 \(i\) 为根的那个集合后面不属于这一集合的第一个点,例如 \({nxt}_3=6\)

然后我们在区间内找到每个集合的第一个点并与前一个集合合并即可。时间复杂度易证(共合并不超过 \(n-1\) 次)。

//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e5+5;

int n, m, nxt[N]; 
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Dsu {
	int fa[N];
	void init(int x) {rep(i, 1, x) fa[i] = i;}
	int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if(u == v) return 0;
		fa[v] = u;
		return 1;
	}
}dsu;

int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, n) nxt[i] = i + 1; 
	dsu.init(n);
	rep(i, 1, m) {
		int op, x, y;
		scanf("%d%d%d", &op, &x, &y);
		if(op == 1) dsu.merge(x, y);
		else if(op == 2) {
			int lst = 0;
			for(int i=x+1;i<=y;i=nxt[i]) {
				dsu.merge(i-1, i);
				if(lst) nxt[lst] = nxt[y];
				lst = i;
			}
			nxt[lst] = nxt[y];
		}
		else puts(dsu.find(x)==dsu.find(y)?"YES":"NO");
	}
	return 0;
}
posted @ 2022-07-17 16:45  rui_er  阅读(37)  评论(0编辑  收藏  举报