并查集
并查集这个部分还是有点溜,方法简单高效,题型也比较灵活,还是先来书上的操作
这就是并查集的三个基本操作,其中find() 放法, 和unite() 也就是“ 查 ” 和 “ 并 ”
不得不提一哈这篇真的弹 幽迈的博客
经典题 poj 1182
这道题我开始还在那紧到自己研究一个办法,我就是很瓜的那种分情况处理,然后我就发现越分情况讨论情况却多,然后始终都是WA,确实还是只有学书上的方法。但是我分情况讨论为啥就那么麻烦喃,关键就是要维护他们的关系,其实他们就只有三种位置,不管哪个是A还是B、C,反正就是一个吃一个,书上就是对于每个动物都创建3个关系,虽然刚开始的时候有些动物的其他两种位置上的动物还没有确定,但是就不管嘛,先等它在那嘛,反正最后所有的动物都会归类而且之后三个位置,哎~其实我也理解的不是很透,就是那种感觉,确实很溜,反正就是很巧妙的把他们之间那种复杂的关系就自然而然的结合到了一起
对了,还有一个问题就是cin
和 scanf
, 虽然我以前听到说过cin
和 scanf
的效率有差距,但是今天终于深刻的体会到了,嗨呀~真的,不信搞一哈就晓得了,cin
绝对要超时,真的溜。。。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int parent[150005];
int rank[150005] = {0};
int len, length;
int find(int input){
if(input == parent[input])
return input;
else
return parent[input] = find(parent[input]);
}
void unite(int x, int y){
x = find(x);
y = find(y);
if(x == y) return ;
if(rank[x] < rank[y]) {
parent[x] = y;
}else{
parent[y] = x;
if(rank[x] == rank[y]) rank[x] ++;
}
}
int main(){
// cin >> len >> length;
scanf("%d%d", &len, &length);
for(int i = 1; i <= 3*len; i++) {parent[i] = i; rank[i] = 0;}
int D, x, y;
int answer = 0;
for(int i = 0; i < length; i++){
// cin >> D >> x >> y;
scanf("%d%d%d", &D, &x, &y);
if(x<1 || y<1 || x>len || y>len) {answer ++; continue;}
if(D == 1){
if(find(x)==find(y+len) || find(x)==find(y+2*len)) {answer++; continue;}
unite(x, y);
unite(x + len, y + len);
unite(x + 2*len, y + 2*len);
continue;
}
if(D == 2){
if(find(x)==find(y) || find(x) == find(y+len)) {answer++; continue;}
unite(x, y+2*len);
unite(x + len, y);
unite(x + 2*len, y + len);
}
}
cout << answer << endl;
return 0;
}
类似的还有poj1703强龙那难地头蛇 , poj2492
其实并查集顾名思义,就是那两种功能“并” 和 “查”, 把同一类的东西并到一起,然后根据条件来查
poj1456还是比较好想,其实用不用并查集都可以解决,就是找到每个物品的最后期限,然后如果发现当前期限已经有物品了就循环往前,知道发现有空的位置,或者没得的时候就说明找完了
然后就是比较有难度的两道
poj1733
这道题我开始的时候又陷入自己切分情况讨论了,然后越做脑壳越大,后头才突然反应过来,其实就是分两类A 和 B, 和A 为偶数的就并到A里面, 和A为奇数的就放到B里面,所以和B为奇数的又放到A里面,因为和A为奇数差的就是B,然而和B为奇数差的那么肯定和A为偶数,所以不管在复杂的关系,没有什么事一个AB解决不了的。
然后就是要解决太大的问题,因为用并查集的时候,一般都是用挨着的数组,但是这道题中数据量最大有1000000000
,这个就打脑壳了,所以这时候必须要离散化
,离散化
可以把输入进来的数据先排序,然后用map
对输入数据和他们排好序后的坐标建立索引,方便后面的时候并查集
然后又有一个问题,那就是比如说1和3的关系已知,4和5的关系已知,这个时候可以得出1和5的奇偶关系,但是1和3, 3和5他们两个就没法得出1和5的关系了,这对于使用并查集的时候就不方便了,因为因为并查集是两个数相同的时候才好切查,但是上面两个例子又表明相同的时候反而不能当成一个,而只相差1的时候又可以当成一个,这时候就需要一些小技巧了,我想的是把所有给出的条件,前端点 x 2 - 1
然后 后端点 x 2 + 1
, 这样就把这个问题解决了
#include<iostream>
#include<string>
#include<map>
using namespace std;
typedef struct Node{
int x, y;
int value;
}Node;
int len, length;
Node input[5005];
map<int, int> list;
int parent[10005];
int rank[10005];
int find(int input){
if(input == parent[input])
return input;
else
return parent[input] = find(parent[input]);
}
void unite(int x, int y){
x = find(x);
y = find(y);
if(x == y) return ;
if(rank[x] < rank[y]){
parent[x] = y;
}else{
parent[y] = x;
if(rank[x] == rank[y]) rank[x] ++;
}
}
int main(){
cin >> len >> length;
int x, y;
string str;
for(int i = 0; i < length; i++){
cin >> x >> y >> str;
input[i].x = 2*x - 1;
input[i].y = 2*y + 1;
if(str == "even") input[i].value = 0;
else input[i].value = 1;
list.insert(make_pair(input[i].x, 0));
list.insert(make_pair(input[i].y, 0));
}
//to scatter
map<int, int>::iterator start, end = list.end();
int jishu = 0;
for(start = list.begin(); start != end; start++) start->second = jishu++;
//init
len = list.size();
for(int i = 0; i < 2*len; i++) { parent[i] = i; rank[i] = 0;}
int i ;
for(i = 0; i < length; i++){
int x = list.find(input[i].x)->second, y = list.find(input[i].y)->second;
int value = input[i].value;
if(find(x)==find(y)){
if(value != 0) break;
}else if(find(x) == find(y+len)){
if(value != 1) break;
}else{
if(value == 1){
unite(x, y+len);
unite(x+len, y);
}else if(value == 0){
unite(x, y);
unite(x+len, y+len);
}
}
}
// for(int i = 0; i < len; i++) cout << parent[i] << " "; cout << endl;
cout << i << endl;
return 0;
}
poj2912
这道题我整得最久,最后还是想出来没有看答案还是比较满足,这道题很烦就是不晓得到底哪个是那个judge,所以如果以来就直接往并查集中并数据的话后面遇到judge在中间乱出的时候产生了冲突就会比较麻烦,所以我想到就一个一个假设是judge,然后就把所有于这个假设相关的石头剪刀布的结果忽略,然后看最后里面有没有冲突的,如果有,那说明假设的这个是错的,这样一来就好办了,但是又咋个确定是第几步找出judge的喃? 这个整了很久,我有一些猜想都是错的,最后我突然想到,就是在前面一个一个假设中,去找每个错误假设的第一个引起矛盾的语句,找出他们中最大的那个,就是他了,其实我也不是很清楚,就是感觉应该是这样,之所以能查出来谁是judge,是因为起码有两个不同的其他人,和judge石头剪刀布后产生了冲突,因为如果和judge产生冲突的都是同一个人,或者同一群人,那么之中无法找出谁是judge,只有在两堆人中,或者多堆人中,有一个共同的人时,这时候就能确定谁是judge了,所以发现judge的时候一定就是在忽略某一个玩家后(其实这个玩家正好是第一次和judge产生冲突的人之一),然后第一次找到冲突的时候就是了。
#include<iostream>
using namespace std;
typedef struct Node{
int x, y;
char oper;
void change(){
if(oper == '>'){
oper = '<';
int temp = x; x = y; y = temp;
}
}
}Node;
Node input[2005];
int len, length;
int parent[1505], rank[1505];
int find(int input){
if(input == parent[input])
return input;
else
return parent[input] = find(parent[input]);
}
void unite(int x, int y){
x = find(x);
y = find(y);
if(x == y) return ;
if(rank[x] < rank[y]){
parent[x] = y;
}else{
parent[y] = x;
if(rank[x] < rank[y]) rank[x] ++;
}
}
int main(){
while(cin >> len >> length){
for(int i = 0; i < length; i ++) {
cin >> input[i].x >> input[i].oper >> input[i].y;
input[i].change();
}
int jishu = 0, shuliang = 0, answer, answerlevel = -1;
for(jishu = 0; jishu < len; jishu ++){
for(int i = 0; i < 3*len; i++){parent[i] = i; rank[i] = 0;}
int x, y, flag = 0;
int level = -1;
char oper;
for(int i = 0; i < length; i++){
x = input[i].x; y = input[i].y; oper = input[i].oper;
if(x == jishu || y == jishu) continue;
if(oper == '='){
if(find(x)==find(y+len) || find(x)==find(y+2*len)){
flag = 1; level = i; break;
}else{
unite(x, y); unite(x+len, y+len); unite(x+2*len, y+2*len);
}
}else{
if(find(x)==find(y) || find(x)==find(y+2*len)){
flag = 1; level = i; break;
}else{
unite(x, y+len); unite(x+len, y+2*len); unite(x+2*len, y);
}
}
}
if(flag == 0){
answer = jishu;
shuliang ++;
}else{
if(level > answerlevel) answerlevel = level;
}
}
if(shuliang == 0){
cout << "Impossible" << endl;
}else if(shuliang == 1){
cout << "Player "<<answer<<" can be determined to be the judge after "<<answerlevel+1<< " lines" << endl;
}else{
cout << "Can not determine" << endl;
}
}
return 0;
}