题目链接:http://poj.org/problem?id=1182
首先,并查集是一种来管理元素分组情况的数据结构。时间复杂度为O(a(n))//阿克曼函数的反函数,比O(log(n))还要快。
这题先用基础的方法来看,先介绍并查集的基本形式(模板类型):
初始化:
void Init(int n){
for(int i = 1;i <= n*3; ++i){
par[i] = i;//各点本身的父节点初始化本身
rank1[i]=0;//各个点的高度为0;有的编译器直接定义rank会报摸棱两可
}
}
最开始无边。
合并:(为了防止退化)
合并时对于两棵树rank不同,那么从rank小的向rank大的连;
通过路径压缩,使查并集更高效。对于每个节点,直接与他的根节点相连接,这种情况,为了简单,即使树的高度发生变化,也不修改rank的值(本题);
void Unite(int x,int y){//合并x,y类;
x = Find(x);
y = Find(y);
if(x == y) return;
if(rank1[x] < rank1[y])
par[x] = y;
else{
par[y] = x;
if(rank1[x] == rank1[y]) rank1[x]++;
}
}
查询:
找到两个点是否是同一组
int Find(int x){
return x ==par[x] ? x : Find(par[x]);//查找x的祖宗;
}
bool Same(int x,int y){//判断x,y是否是同一类(同一祖宗)
return Find(x) == Find(y);
}
分割线
本题这种方法巧妙在他对3种关系同时进行了管理,化时间为空间。
//#include<bits/stdc++.h>
#include<iostream>
using namespace std;
const int maxn = 500010;//数组因为是n 3倍,所以还是尽量开大点;
int par[maxn];//父节点
//int rank1[maxn];//高度
void Init(int n){ //并查集初始化
for(int i = 1;i <= n*3; ++i){
par[i] = i;//各点本身的父节点初始化本身
//rank1[i]=0;
}
}
int Find(int x){
return x ==par[x] ? x : Find(par[x]);//查找x的祖宗;
}
void Unite(int x,int y){//合并x,y类;
x = Find(x);
y = Find(y);
if(x == y) return;
par[x] = y;
}
bool Same(int x,int y){//判断x,y是否是同一类(同一祖宗)
return Find(x) == Find(y);
}
int N,K;
int main(){
//ios::sync_with_stdio(false); 这题好像不吃关闭输入流 ,所以还是老老实实scanf;
cin>>N>>K;
int ans = 0;
Init(N*3);//初始化3段,0-N代表x->A(x是A类),N~2N,代表x->B,2N~3N,代表x->C;分3段管理
for(int i = 0;i<K; ++i){
int t,x,y;
//cin>>t>>x>>y;
scanf("%d%d%d",&t,&x,&y);//老老实实scanf;
if(x<=0 || N<x || y<=0 || N<y){//可直接判断错误的条件,小于0或大于N
ans++;continue;
}
if(t == 1){//合并类,先判断,同类
if(Same(x,y+N) || Same(x,y+2*N)){//如果找到x,y是不同类,错误
ans++;
}
else{
Unite(x,y);//因为x,y是同类,所以x+N和y+N是同类;
Unite(x+N,y+N);
Unite(x+N*2,y+N*2);
}
}
else{//x吃y
if(Same(x,y) || Same(x,y+2*N)){//如果找到x和y是同类或者x被y吃 ,错误
ans++;
}
else{
Unite(x,y+N);//同理
Unite(x+N,y+2*N);
Unite(x+2*N,y);
}
}
}
cout<<ans<<endl;
return 0;
}
这题还有向量思维模式 ,(2);
最后:
programming is the most fun you can have with your clothes on.