[kuangbin带你飞]专题五 并查集
作者:互联网
目录
- A - Wireless Network POJ-2236
- B - The Suspects POJ 1611
- C - How Many Tables HDU1213
- D - How Many Answers Are Wrong HDU 3038
- E - 食物链
- F - True Liars
- G - Supermarket
- H - Parity game
- I - Navigation Nightmare
- J - A Bug's Life
- K - Rochambeau
- L - Connections in Galaxy War
- M - 小希的迷宫
- N - Is It A Tree?
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 ]
- a,b在同一集合,区间和是否为s
- 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。
若查询为假:
- 与前面的真话冲突
- x or y > n
- 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]中,判断两端点的根节点是否相同
- 相同:已知奇偶性,直接判断
- 不同:合并子树。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