并查集
洛谷P3367
以后要注意的:
- scanf函数括号里面要写好,先双引号" " ,再用逗号,将输入内容和变量分开
洛谷P1525
学到了:
- sort函数的第三个参数,该参数是一比较参数,决定排序完数据是升序还是降序
cpp
bool cmp(int a,int b){
return a>b;//可以理解为当a>b时把a放在b前面,就是升序
//return a<b;可以理解为当a<b时把a放在b前面,就是降序
}
- 结构体数组排序
- 先定义结构体
cpp
struct node{
int a,b,c;
}x[10];
- 输入数据
cpp
for(int i=1;i<=m;i++){
scanf("%d %d %d",&x[i].a,&x[i].b,&x[i].c);
}
3.设立比较条件
- 一级排序(将c从大到小排序)
cpp
bool cmp(node i,node j){
return i.c>j.c;
}
- 二级排序(c相等的情况下,按照b大小来从小到大排序)
cpp
bool cmp(node i,node j){
if(i.c==j.c){
return i.b>j.b;
}else{
return i.c>j.c;
}
}
以后要注意的:
- 种类并查集就是找出有哪些不同种类(不同集合)
- 在一数组上划分不同的状态(比如
i为正状态,则i+n为反状态,两者不共存) - 当题目要求分开x和y时,我们就需要合并x和y的反状态,y和x的反状态
- 判断是否已经区分就是查看是否在一个集合(普通并查集),找父节点
洛谷CF776D
思路:
怎么想到用并查集的?
-
我们已经得知每盏灯的初始状态和哪两个开关控制它。
-
首先分类讨论:
-
如果最开始这盏灯是开着的,那么这两个开关应该做出同样的操作,即要么全按,要么全不按。
-
如果最开始这盏灯是关着的,那么这两个开关应该做出不同的操作,即一个按一个不按。
-
转化一个思路,就变成了有 m 个数,其中有 n 个关系,有些关系是两个数要一样,有些关系是两个数要不一样,问是否合理。
-
-
维护开与关的关系,我们想到了并查集。
该题目最重要的一句话:每扇门恰好被两个开关控制
而每扇门只有两个状态:
- 0状态,房间门是锁着的
- 1状态,房间门的开着的
依题意现在每一扇门对应两个钥匙,设门为开 的状态为(u,v),设门为关 的状态为(u+m,v+m)且一共有m个开关数,则:
-
对于开着的门:
合并
(u,v),(u+m,v+m)- 同种状态钥匙开一遍才能保持门是"开"的状态
-
对于关着的门:
合并
(u,v+m),(u+m,v)- 不同种状态的钥匙开一遍才能保证门是关着的
当合并完之后,我们只需要遍历所有开关数 i ,当发现i与i+m在同一集合,则输出 "NO",因为一把钥匙只能有使用或不使用的状态.所以如果i和i+m在一个并查集内,这是不合法的
这一流程用并查集文字意思就是:
- 遍历每一个门的时候赋予每一个开关开或不开的状态,当有一个开关矛盾了(就是开和不开同时需要满足),则依题意输出 "NO"。
这里的输入流程比较麻烦:
- 输入房间数和开关数
- 输入房间状态(基于房间数)
- 输入控制房间数量
- 输入钥匙控制房间的编号(基于第3点)
- 这里要用一个结构体数组来存数据,数组下标是房间号,结构体里面是能控制对应房间号的两个钥匙
cpp
struct key{
int a,b;
}x[200005];
for(int i=1;i<=m;i++){ //i是开关编号
int c;
scanf("%d",&c); //控制的房间数量
for(int j=1;j<=c;j++){
int d;
scanf("%d",&d); //被控制的房间编号
if(x[d].a==0){
x[d].a=i; //房间编号d由2个开关中的i开关控制
}else{
x[d].b=i;
}
}
}
}
以后要注意的:
- 处理并查集时始终用一个函数来合并集合,避免直接修改父节点数组(或者修改完父节点数组后再次重新查找父节点),因为这会破坏路径压缩,导致循环引用或造成错误的集合关系,破坏了并查集结构