并查集

并查集的两个基本操作:
\(1.\) 查询一个元素属于哪个集合 \(O(logn)\)
\(2.\) 把两个集合合并成一个集合 \(O(logn)\)

实现方法: 建立树形结构存储,树上的节点都是一个元素
查询时只需要沿着路径向上搜索
合并时只需要把根节点和根节点相联通(使一个节点作为一个节点的父亲)

两个优化:
\(1.\) 路径压缩
因为如果是单链的情形,复杂度会飙升,但如果把访问的节点直接指向树根,就节省了沿路径走的时间
\(2.\) 按秩合并
秩:衡量集合(树)大小标志,可以是树的深度,也可以是集合的大小
合并时把秩小的接到秩大的上,只增加小的结构的查询代价
别名:启发式合并

void init() {
  for (int i = 1; i <= n; i++) fa[i] = i ;
}

int get(int x) {
	if (x == fa[x]) return x ;
	return fa[x] = get(fa[x]) ;
}

void merge(int x, int y) {
	fa[get(x)] = get(y) ;
} 

程序自动分析 NOI2015

这个例题说明:并查集可以动态维护节点之间的连通性和传递性

离散化 等同合并 查询不等关系 不等的在一组直接 \(NO\)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
using namespace std ;

typedef long long ll ;
const int N = 1000010 ;

int n, m, T ;
int a[N], b[N], w[N] ;
int fa[2 * N] ;
map <int, int> M ;

int get(int x) {
	if (x == fa[x]) return x ;
	return fa[x] = get(fa[x]) ;
}

void merge(int x, int y) {
	fa[get(x)] = get(y) ;
} 

int main() {
	scanf("%d", &T) ;
	while (T--) {
		scanf("%d", &m) ;
		n = 0 ;
		M.clear() ;
		for (int i = 1; i <= m; i++) scanf("%d%d%d", &a[i], &b[i], &w[i]) ;
		// 1 =   0 !
		for (int i = 1; i <= m; i++) {
			if (M.find(a[i]) == M.end()) M[a[i]] = ++n, a[i] = n ;
			else a[i] = M[a[i]] ;
			if (M.find(b[i]) == M.end()) M[b[i]] = ++n, b[i] = n ;
			else b[i] = M[b[i]] ;
		}
		for (int i = 1; i <= n; i++) fa[i] = i ;
		for (int i = 1; i <= m; i++) if (w[i] == 1) merge(a[i], b[i]) ;
		bool flag = true ;
		for (int i = 1; i <= m; i++)
		if (w[i] == 0) {
			if (get(a[i]) == get(b[i])) {
				flag = false ;
				break ;
			}
		}
		puts(flag ? "YES" : "NO") ;
	}
}

Supermarket \(POJ_1456\)

这个题的并查集挺有意思 维护的是一个数组中位置的占用情况
贪心思路:优先考虑利润大的商品,且对每个商品尽量晚的卖出(决策包容性)
对天数做并查集,对于一个商品,如果在 \(d\) 天过期,就查询 \(d\) 的树根 \(rt\)
假如 \(rt\)\(0\) 说明前\(d\)天已经塞满,没有时间空余,该物品不考虑
如果 \(rt\) 不为 \(0\) 则取走该物品,把\(rt\)\(rt-1\)合并
某一个节点的根节点的实际意义是 这颗树(其实是区间)最晚卖出该物品的时间,或者从后往前第一个空闲的日期

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std ;

typedef long long ll ;
const int N = 10010 ;

struct node {
	int p, d ;
} ;

bool cmp(node a, node b) {
	if (a.p != b.p) return a.p > b.p ;
	else return a.d > b.d ;
}

int n, mxd, ans ;
node a[N] ;
int fa[N] ;

int get(int x) {
	if (fa[x] == x) return x ;
	else return fa[x] = get(fa[x]) ;
}

void merge(int x) {
	fa[get(x)] = get(x - 1) ;
}

int main() {
	while (scanf("%d", &n) != EOF) {
		ans = 0 ; mxd = 0 ;
		for (int i = 1; i <= n; i++) {
			scanf("%d%d", &a[i].p, &a[i].d) ;
			mxd = max(mxd, a[i].d) ;
		}
		sort(a + 1, a + n + 1, cmp) ;
		for (int i = 1; i <= mxd; i++) fa[i] = i ;
		for (int i = 1; i <= n; i++) {
			int r = get(a[i].d) ;
			if (r > 0) {
				ans += a[i].p ;
				merge(r) ;
			}
		}
		printf("%d\n", ans) ;
	}
}

NOI 2002 银河英雄传说

这个题除了要并查集维护在同一集合之中,还需要查询从一个节点到另一个节点之间的距离

因为集合是树状的,两个节点 \(i,j\) 之间的距离可以转化为 \(i\)\(root\) 的距离和 \(j\)\(root\) 距离的差值
距离可以通过一遍 \(get\) 获取,完事之后路径压缩不影响结果

然后再考虑合并操作
\(i\) 整列拼到 \(j\) 的列的下面,等价于把 \(i\) 的根 \(x\) 变成 \(j\) 的根 \(y\) 的一个子节点,而且 \(x\)\(y\) 的距离要初始成当前 \(y\)的树大小
于是要新增一个记录子树大小的数组,合并之后更新大(新)树的根节点

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std ;

typedef long long ll ;
const int N = 500010 ;
const int M = 30010 ;

int n, T ;
int a[N], b[N], fa[M], d[M], size[M] ;
char w[N] ;

int get(int x) {
	if (x == fa[x]) return x ;
	int rt = get(fa[x]) ;
	d[x] += d[fa[x]] ;
	return fa[x] = rt ;
}

void merge(int x, int y) {
	x = get(x), y = get(y) ;
	fa[x] = y ; d[x] = size[y] ;
	size[y] += size[x] ; 
}

char getch() {
	while (true) {
		char t ;
		scanf("%c", &t) ;
		if (isalpha(t)) return t ;
	}
}

int main() {
	int T ; scanf("%d", &T) ;
	for (int i = 1; i <= T; i++) {
		w[i] = getch() ;
		scanf("%d%d", &a[i], &b[i]) ;
	}
	for (int i = 1; i <= T; i++) n = max(n, max(a[i], b[i])) ;
	for (int i = 1; i <= n; i++) fa[i] = i, d[i] = 0, size[i] = 1 ;
	for (int i = 1; i <= T; i++) {
		if (w[i] == 'M') {
			merge(a[i], b[i]) ;
		} else {
			if (get(a[i]) != get(b[i])) puts("-1") ;
			else {
				printf("%d\n", abs(d[a[i]] - d[b[i]]) - 1) ;
			}
		}
	}
}

Parity game POJ1733

NOI2001 食物链

posted @ 2023-08-12 21:35  哈奇莱特  阅读(36)  评论(0)    收藏  举报