算法笔记-并查集

并查集

维护一下不相交的集合,支持两种操作:合并两个集合,查询一股元素所处的集合

路径压缩
沿着树根找到 a a a 元素所在集合的代表 b b b 后,对这一条路径上的所有元素 x x x f a [ x ] = b fa[x] = b fa[x]=b

按秩合并
深度较小的合并到深度较大的集合上。每个集合维护一个 r a n k rank rank 值,将 r a n k rank rank 小的合并到 r a n k rank rank 大的集合,如果 r a n k rank rank 相等,则 r a n k = r a n k + 1 rank = rank + 1 rank=rank+1

struct DSU{
	vector<int> fa;
	vector<int> rank;
	DSU(int n) : fa(n), rank(n){
		for(int i = 1; i <= n; i++)
			fa[i] = i;
	}
	int find(int x){
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}
	void merge(int x, int y){
		int fx = find(x);
		int fy = find(y);
		if(rank[fx] < rank[fy])
			fa[fx] = fy;
		else{
			fa[fy] = fx;
			if(rank[fx] == rank[fy])
				rank[fx]++;
		}
	}
	
};

P1525 [NOIP2010 提高组] 关押罪犯

题意:

n n n 名罪犯和 2 2 2 座监狱。如果两名罪犯之间的仇恨值为 c c c ,且在同一监狱,则造成影响为 c c c 的冲突事件。

询问,如何分配罪犯,使最大的冲突值最小

解析:

c c c 降序排序,依次处理每组罪犯 ( a , b ) (a,b) (a,b)

  • 如果 a , b a,b a,b 已经在一个监狱,答案为 c c c
  • a , b a,b a,b 分配到两个监狱

对于需要分配的情况,每组都有两种分配的方法,但注意到 b b b 的两个敌人在同一监狱,记录每个人的敌人。分配监狱时,把 a a a 分配到 b b b 的敌人所在监狱, b b b 同理

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
struct peo{
	int a, b, c;
	bool operator < (const peo &x) const{
		return c > x.c;
	}
}a[maxn];
int f[maxn];
int find(int x){
	return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y){
	int fx = find(x);
	int fy = find(y);
	if(fx != fy)
		f[fx] = fy;
	return;
}
int b[maxn];
int n, m;
int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
		f[i] = i;
	for(int i = 1; i <= m; i++)
		cin >> a[i].a >> a[i].b >> a[i].c;
	
	sort(a+1, a+1+m);
	for(int i = 1; i <= m; i++){
		int fx = find(a[i].a);
		int fy = find(a[i].b);
		if(fx == fy){
			cout << a[i].c << endl;
			return 0;
		}
		else{
			if(!b[a[i].a])
				b[a[i].a] = a[i].b;
			else
				merge(a[i].b, b[a[i].a]);
			if(!b[a[i].b])
				b[a[i].b] = a[i].a;
			else
				merge(b[a[i].b], a[i].a);
		}
	}
	cout << 0 << endl;
	return 0;
}



P2024 [NOI2001] 食物链

题意:

三类动物 A , B , C A, B, C A,B,C , A A A B B B B B B C C C C C C A A A

n n n 个动物,和 k k k 句话。每句话有两种描述: X X X Y Y Y 是同类; X X X Y Y Y

如果某一句话与前边真话冲突,则是假话,询问假话数目

解析:

3 3 3 倍并查集存关系,一倍存本身,二倍存食物,三倍存天敌

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;


struct DSU{
	vector<int> fa;
	vector<int> rank;
	DSU(int n) : fa(n), rank(n){
		for(int i = 1; i <= n; i++)
			fa[i] = i;
	}
	int find(int x){
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}
	void merge(int x, int y){
		int fx = find(x);
		int fy = find(y);
		if(rank[fx] < rank[fy])
			fa[fx] = fy;
		else{
			fa[fy] = fx;
			if(rank[fx] == rank[fy])
				rank[fx]++;
		}
	}
	
};
//1倍本身 2倍食物 3倍天敌 
int n, k, ans;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> k;
	DSU dsu(3*n);
	
	int op, x, y;
	for(int i = 1; i <= k; i++){
		cin >> op >> x >> y;
		if(x > n || y > n){
			ans++;
			continue;
		}
		if(op == 1){
			if(dsu.find(x)==dsu.find(y+n) || dsu.find(x)==dsu.find(y+2*n)){
				ans++;
				continue;
			}
			dsu.merge(x, y);
			dsu.merge(x+n, y+n);
			dsu.merge(x+2*n, y+2*n);
		}
		else{
			if(x == y){
				ans++;
				continue;
			}
			if(dsu.find(x)==dsu.find(y) || dsu.find(x+2*n)==dsu.find(y)){
				ans++;
				continue;
			}

			dsu.merge(x, y+2*n);
			dsu.merge(x+n, y);
			dsu.merge(x+2*n, y+n);
		}
	}
	cout << ans << endl;
	return 0;
}



P1682 过家家

题意:

n n n 个男生, n n n 个女生。有些女生之间是朋友关系,有些男生和女生之间是不吵架关系,朋友关系可以传递。女生可以和不吵架的男生玩一轮游戏,也可以和朋友女生不吵架的男生完。

每个女生最多可以和 k k k 个吵架的男生玩游戏

询问做多玩几轮游戏

解析:

女生朋友关系用并查集维护,记录每个并查集连通块对应的吵架的男生数目。

连通块对应男生数目最小值为不使用“强制”轮数,加上k即最多轮数,最后和 n n n 取min

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define mkp(a, b) make_pair((a), (b))
const int maxn = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;

struct DSU{
	vector<int> fa;
	vector<int> rank;
	DSU(int n) : fa(n), rank(n){
		for(int i = 1; i <= n; i++)
			fa[i] = i;
	}
	int find(int x){
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}
	void merge(int x, int y){
		int fx = find(x);
		int fy = find(y);
		if(rank[fx] < rank[fy]){
			fa[fx] = fy;
		}			
		else{
			fa[fy] = fx;
			if(rank[fx] == rank[fy])
				rank[fx]++;
		}
	}	
};
set<int> cnt[maxn];
vector<pii> boygirl;
int n, m, k, f;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	

	cin >> n >> m >> k >> f;
	DSU dsu(2*n);
	
	for(int i = 1; i <= m; i++){
		int a, b;
		cin >> a >> b;
		boygirl.push_back(mkp(a, b));
	}
	for(int i = 1; i <= f; i++){
		int a, b;
		cin >> a >> b;
		dsu.merge(a, b);
	}
	
	for(int i = 0; i < boygirl.size(); i++){
		pii s = boygirl[i];
		int girl = s.fi;
		int boy = s.se;		
		cnt[dsu.find(girl)].insert(boy);
	}
	int res = INF;
	for(int i = 1; i <= n; i++){
		int siz = cnt[dsu.find(i)].size();
		res = min(res, siz);
	}
	res = min(res+k, n);
	cout << res << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值