其他分享
首页 > 其他分享> > [kuangbin带你飞]专题五 并查集

[kuangbin带你飞]专题五 并查集

作者:互联网

目录

A - Wireless Network POJ-2236

​ 题目中给了n台计算机的位置,计算机最大可连接距离d,进行一些操作 O p:修复编号为p的计算机;S p q:测试编号为p,q的两台计算机是否可以连接。

思路:

​ 由于一个计算机可借助连接的计算机连接到d之外的计算机上,即连接的计算机可以组成一些集合,集合中的计算机可连接,维护这些集合中元素的联系 -- 并查集。计算机之间唯一的关系:连接。先将所有计算机的位置存储起来,在接下来的若干次操作中,O操作:相对于p在距离d之内可直接连接的已修复计算机a来说,a所在集合中的所有计算机都可以连接到p上,即将集合a的代表节点指向p的代表节点。 S操作:判断p,q计算机是否可以互相连接,判断其代表节点是否相同(是否在同一集合)。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define x first
#define y second
#define endl '\n'
using namespace std; 
const int kN = 1010;
int n,d,p,q,f[kN];
pair<double,double> g[kN];
bool active[kN]; 
void init(){
	for(int i = 1; i <= n; i++) f[i] = i;
} 

int find(int x){
	int r = x,t;
	while(r != f[r]) r = f[r];//寻找根节点
	while(x != r){
		t = f[x];
		f[x] = r;
		x = t;
	} 
	return r;
}

bool judge(int x,int y){
	if(find(x) == find(y)) return true;
	return false;
}

void join(int x,int y){
	int rx = find(x),ry = find(y);
	if(f[rx] != f[ry]) f[rx] = f[ry];
}

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	memset(active,false,sizeof(active));
	char c;
	cin >> n >> d;
	init();
	for(int i = 1; i <= n; i++) cin >> g[i].x >> g[i].y;
	while(cin >> c){
		if(c == 'O'){
			cin >> p;
			active[p] = 1;
			//p加入可通信的网络中 
			for(int i = 1;i <= n; i++){
				if(active[i] && sqrt((g[i].x - g[p].x) * (g[i].x - g[p].x) + (g[i].y - g[p].y) * (g[i].y - g[p].y)) <= d){
					join(i,p);//所有可连接的计算机,将代表节点修改为p的 
				}
			}
		}else if(c == 'S'){
			cin >> p >> q;
			if(judge(p,q)) cout << "SUCCESS" << endl;
			else cout << "FAIL" << endl;
		}else break;
	}
	return 0;
}
B - The Suspects POJ 1611

​ n个人,组成m个小组。题目中给出了这些小组中具体人员名单,开始时0号为疑似病例,与0号直接、间接接触的人员为疑似病例。最后询问有多少个疑似病例?

思路

​ 使用并查集,在输入小组信息成员时,首元素为代表元素,小组中其他成员所在集合与代表元素所在集合合并。最后确定0所在集合的代表元素,以及这个集合中的人数

	while(cin >> n >> m){
		if(!n && !m) break;
		init();
		ans = 0;
		for(int i = 0; i < m; i++){
			cin >> k >> r;//首元素代表 
			for(int j = 1; j < k;j++){
				cin >> t;
				join(t, r);//t所在集合加入r所在集合 
			} 	
		}
		int r = find(0);
		for(int i= 0; i < n; i++){
			if(find(i) == r) ++ans;
		}
		cout << ans << endl;
	}
C - How Many Tables HDU1213

​ 直接 or 间接认识的朋友在一个表中(同一集合),n个朋友,m组关系a - b,询问需要多少张表(多少个集合)?

思路:

​ 在输入关系式,a所在集合加入到b所在集合中,在m次输入后统计集合数即可。

map<int,bool> book;//记录不同的代表元素

	int a,b;
	cin >> t;
	while(t--){
		book.clear();
		cin >> n >> m;
		init();
		for(int i = 0; i < m; i++){
			cin >> a >> b;
			join(a,b);
		}
		for(int i = 1; i <= n; i++){
			if(!book[find(i)]) book[find(i)] = true;
		}
		cout << book.size() << endl;
	}
D - How Many Answers Are Wrong HDU 3038

​ TT写下了n个数,在FF的m次询问中,每次回答一个区间范围[a,b]和区间和sum,m次询问中存在一些回答是错误的,求出错误的回答次数。

思路:

​ m次回答中,每次均输入a,b,s三个数,表示闭区间[a,b]的和为s,即a,b间存在一个权值s。在并查集中,对于处于同一集合中的元素,各个权值已知 or 可以推算!推算(a,b]+(b,c] = (a,c],首先处理a:--a;

​ 定义sum[],记录各个节点到根节点的和,区间和:sum[b] - sum[a ]

  1. a,b在同一集合,区间和是否为s
  2. a,b不在同一集合,a,b集合合并 a - ra,b - rb,b加到a -- sum[rb] = sum[a] + s - sum[b];//rb到a的代表元素的距离为:a,b间距加上a-ra间距及再减去b - rb的距离
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std; 
const int kN = 2e5 + 5;
int ans,n,m,a,b,s,f[kN],sum[kN];
void init(){
	memset(sum,0,sizeof(sum));
	for(int i = 1; i <= n; i++) f[i] = i;
} 
int find(int x){
	if(x != f[x]){
		int t = f[x];
		f[x] = find(f[x]);
		sum[x] += sum[t];
	}
	return f[x];
}
void join(int x,int y,int s){
	int rx = find(x),ry = find(y);
	f[ry] = rx;
	sum[ry] = s + sum[x] - sum[y];
} 

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	while(scanf("%d%d",&n,&m) != EOF){
		ans = 0;
		init();
		for(int i = 0; i < m; i++){
			cin >> a >> b >> s;
			--a;
			if(find(a) == find(b)){
				if(sum[b] - sum[a] != s) ++ans;
			}else join(a, b, s);
		}	
		cout << ans << endl;
	}
	return 0;
}
E - 食物链

​ n个数,k个查询,每个查询形如d x y, d == 1, x y 同类;d == 2,x吃y;三类动物a吃b,b吃c,c吃a。

若查询为假:

  1. 与前面的真话冲突
  2. x or y > n
  3. x 吃 x 假话

​ 对于条件2,3在输入的过程中即可确定。子节点与父节点的关系只有捕食、被捕食、同类三种,互相间的关系可通过捕食或被捕食来确定。x,y父节点相同,判断关系;父节点不同,合并集合。使用加权并查集,权值即为节点间的关系。路径压缩时节点间关系修改:(kid.r + father.r)% 3即为子节点与爷爷节点的关系,做的时候这个关系一直没推出来,最后看了别人的题解发现将子节点与父节点,子节点与爷爷节点间的所有关系枚举出来后这个关系也就出来了。

kid & father father & grandfather kid & grandfather
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std; 
const int kN = 50005;
int ans,n,k,d;
struct node{
	int p,r;//存储父节点编号,与父节点的关系 0 同类  1 被吃 2 吃父节点 
}f[kN];
void init(){
	ans = 0;
	for(int i = 1; i <= n; i++) f[i].p = i,f[i].r = 0;
} 
//路径压缩,与父节点关系改变 
int find(int x){
	if(x == f[x].p) return f[x].p;
	else{
		int t = f[x].p;
		f[x].p = find(f[x].p);
		f[x].r = (f[x].r + f[t].r) % 3; 
		return f[x].p;
	}
}
void join(int x,int y){
	--d;//0同类 1被吃 2 吃 
	int rx = find(x), ry = find(y);
	f[ry].p = rx;
	f[ry].r = (3 - f[y].r + d + f[x].r) % 3;
} 

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int x,y;
	scanf("%d%d",&n,&k);
	init();
	for(int i = 0; i < k; i++){
		scanf("%d%d%d",&d,&x,&y);
		if(x > n || y > n){
			++ans;
		}else if(d == 2 && x == y){
			++ans;
		}else if(find(x) == find(y)){
			if(d == 1 && f[x].r != f[y].r) ++ans;//x y 同类  相对于代表元素关系相同 
			if(d == 2 && f[x].r != (f[y].r + 2) % 3) ++ans;//y - x 2
		}else join(x,y);
	}
	cout << ans << endl;
	return 0;
}
F - True Liars

​ 好人一个集合,坏人一个集合。在询问中,好人说真话,坏人说假话。因此,x说y是好人时,若x是好人,则y也是好人;否则,两人均为坏人。x说y是坏人,若x是好人,则y为坏人;否则,相反。即:x y yes中 x y 在同一集合,x y no中 x y 不在同一集合。使用加权并查集,与父节点:0同类,1异类。将所有人分为若干个集合,每个集合中又分为好人、坏人两个。

​ dp(i,j)表示,前i个大集合中好人为j个的方案数,从初始状态不断向后递推,最后判断dp(n,p1)是否为1.

G - Supermarket

​ 买卖n件东西,每件物品有一个截止售卖时间,每个单位时间只能买一件,问最大获利。

​ 贪心,先按价值降序。如果截止的那一天可以卖就那一天,不可以的话,向之前寻找最近的可购买时间。(使用并查集快速寻找最靠近截止时间的可购买物品时间点。根大于0,表示存在这个时间点)

H - Parity game

​ map标记离散 + 加权并查集

I - Navigation Nightmare
J - A Bug's Life

​ 没有gay的情况下,不同性别的两个bug才能在一起。给你几对在一起的bug,问里面有没有gay。

K - Rochambeau

​ n个人玩石头剪刀布,其中一人为裁判,剩下的n - 1人分为3组。每一组提前选定出什么手势,裁判的手势任意。问:是否能从给出的一系列游戏结果中,找到裁判的手势。

​ 答题思路和食物链差不多,分0,1,2三组。由于裁判手势任意,先假设每个人都为裁判,然后通过并查集判断不涉及裁判的游戏中是否有矛盾的情况,没有的话可能是裁判,记录位置;有矛盾则不是裁判。最后统计一下可能是裁判的位置个数,若为0,木有得;为1,输出;为2,可能。

L - Connections in Galaxy War

​ 各个星球间有不同的能量值,互相存在通道。当有星球被攻击时,通过通道寻找能量值最高的星球帮忙,有一些通道被破坏了...

思路:

​ 逆向并查集,先存储所有的输入,从后向前枚举,destroy看作是加入一个通道,将所有被破坏的通道连接起来。初始状态:在m个关系里扣除要删除的边,建立集合。join(),维护一个最值做为结点的值。

M - 小希的迷宫
设计一个迷宫,两房间只存在一个通道连接。给一个设计图,判断是否符合条件(是否有环)。

思路:

​ 对输入的顶点,判断是否在同一个集合,如果在,则存在多条通道。对于所有的顶点,在最后位于同一集合中。记录每个顶点是否使用,最后通道数与顶点数差值为1.

N - Is It A Tree?

​ 判断是否为一棵树

思路:

标签:专题,int,sum,查集,kuangbin,集合,include,节点
来源: https://www.cnblogs.com/honey-cat/p/12837976.html