ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2020-05-06 18:01:46  阅读:298  来源: 互联网

标签:专题 int sum 查集 kuangbin 集合 include 节点


目录

A - Wireless Network POJ-2236

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

  • 在这道题目中可以增加一个active数组记录各个编号的计算机是否已经修复好可以使用。

思路:

​ 由于一个计算机可借助连接的计算机连接到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号在同一小组
  • 间接接触:与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标记离散 + 加权并查集

  • 使用数组s[x]记录(x的根节点,x]区间中1de个数的奇偶性,0 偶数, 1 奇数

  • 区间(a,b]中,判断两端点的根节点是否相同

    1. 相同:已知奇偶性,直接判断
    2. 不同:合并子树。s[f[a]] = s[a] ^ s[b] ^ s((a - 1,b]),路径压缩s[i] ^= s[f[i]]
I - Navigation Nightmare
  • 依然是一道加权并查集,给出了若干点,以及点之间的曼哈顿距离(|xa - xb| + |ya - yb|) -- m个询

  • 问z条命令之后两点间的曼哈顿距离

    ​ 加权并查集的向量转移,注意离线操作、路径压缩,插入一个点,维护权值数组(x,y坐标)

    • W,E,N,S时x,y的变化
    • 按照查询顺序输出答案
J - A Bug's Life

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

  • 加权并查集,和前几道题做法类似。使用权值r存储与父节点的关系,0 同性,1 异性

  • 读入x,y,判断是否为同一集合

    • 是:是否同性
    • 否:合并集合
  • 这道题还可以用搜索做,待补充...

K - Rochambeau

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

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

L - Connections in Galaxy War

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

  • destroy a b:a b间的通道被破坏
  • query a:a能否找到救援(能量值高于自己)

思路:

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

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

思路:

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

N - Is It A Tree?

​ 判断是否为一棵树

思路:

  • 一棵树中:无环,只有一个根节点,最后判断根节点数目即可

  • 空树也是树

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有