poj 1182 食物链 (并查集)

时间:2024-06-11 15:35:14

http://poj.org/problem?id=1182

关于并查集 很好的一道题,开始也看了一直没懂。这次是因为《挑战程序设计竞赛》书上有讲解看了几遍终于懂了。是一种很好的思路,跟网上其他的不太一样。

因为N和K很大,所以必须高效维护动物之间的关系,并快速判断是否产生了矛盾,并查集是维护 "属于同一组"的数据结构,,但是在本题中,并不是只有属于同一类的信息,

还有捕食关系的存在,因此需要开动脑筋维护这些关系。

对于每只动物 i 创建3个元素 i-A,i-B,i-C,并用着3×N个元素建立并查集。这个并查集维护如下信息:

i-x 表示 ”i属于种类 x“;

并查集里的每一组表示组内所有元素代表的情况都同时发生或不发生。

例如,如果i-A和j-B 在同一个组里,就表示如果i属于种类A那么j一定是属于种类B,如果j属于种类B,那么i一定属于种类A,因此,对于每一条信息,只需要按照下面进行操作就可以

第一种:x和y属于同一种类,合并x-A和y-A,x-B和y-B,x-C和y-C.

第二种:x吃y                    合并x-A和y-B,x-B和y-C,x-C和y-A.

不过在合并之前,需要判断合并是否会产生矛盾,例如在第一种信息的情况下,需要检查比如x-A和y-B或者y-C是否在同一组的等信息。

 #include <cstdio>
const int maxn = ; int par[maxn]; //父亲
int rank[maxn]; //树的高度 int N,K;
int T[maxn],X[maxn],Y[maxn]; //T是类型
//初始化n个元素
void init(int n) {
for(int i=; i<n;i++) {
par[i]=i;
rank[i]=;
}
}
//查询树的根
int find(int x) {
if(par[x]==x) {
return x;
}
else return par[x]=find(par[x]);
}
//合并x和y所属集合
void unite(int x,int y) {
x=find(x);
y=find(y);
if(x==y) return; if(rank[x] < rank[y]) {
par[x]=y;
} else {
par[y]=x;
if(rank[x]==rank[y]) rank[x]++;
}
}
//判断x 和y是否属于同一个 集合
bool same(int x,int y) {
return find(x) == find(y);
} void solve() {
//初始化并查集
//元素 x,x+N,x+2*N分别代表x-A,x-B,x-C
init(N*); int ans=;
for(int i=;i<K;i++) {
int t=T[i];
int x=X[i]-,y=Y[i]-;
if(x<||N<=x||y<||N<=y) { //不正确的编号
ans++;
// printf("%d %d\n",x,y);
continue;
} if(t==) {
//x和y属于同一类 并且每次都是3个集合一起合并,所以只需要判断一种情况即可。
if(same(x,y+N)||same(x,y+*N)) { //竟然x和y是同一种类,那么x和y+N,x和y+2N必定不能是同一种
ans++;
}
else { //合并 两个种类,注意我们不清楚 x和y具体是哪个种类,所以必须全部合并
unite(x,y);
unite(x+N,y+N);
unite(x+N*,y+N*);
}
}
else {
//x吃y
if(same(x,y)||same(x,y+*N)) { //x和y在同一组,或者是A,C的情况
ans++;
}
else { //A吃B,B吃C,C吃A
unite(x,y+N);
unite(x+N,y+*N);
unite(x+*N,y);
}
}
}
printf("%d\n",ans);
} int main() {
//freopen("a.txt","r",stdin);
scanf("%d%d",&N,&K);
for(int i=;i<K;i++)
{
scanf("%d%d%d",&T[i],&X[i],&Y[i]);
// printf("%d %d %d\n",T[i],X[i],Y[i]);
}
solve();
return ;
}