POJ1182 食物链 (带权并查集)
一看见这道题就想到了并查集,但是普通的并查集根本行不通,因为通过普通的并查集我们只能得出两个元素是否在同一个集合与把两个集合合并为
一个集合,这道题既有种类之分,各个种类之间还有同类、吃、被吃的关系。限时训练结束后学长说这道题用带权并查集,百度了n个博客,才有了点
头绪。
首先,带权并差集比普通的并查集多了一个权值(关系),普通并查集只用了一个pre数组存储并查集的父子关系,pre[1]=2表示1为2的父节点,而
带权并查集还用了一个relation数组存储两个点的关系。
在这道题里,用re数组表示与父节点的关系,0表示与父节点同类,1表示被父节点吃,2表示吃父节点,在初始化时,因为pre[i]=i,所以re[i]初始化
为零,因为本身就是自己的父节点
只要是我们确定了两个动物的关系,就把他们连在一起,也就是说, 并查集中的每一个元素均是有关系的,带权并查集的关键还是路径压缩与集合
的合并,如何在路径压缩与集合合并时更新关系是难点,在路径压缩时我们需要根据子节点与父节点的关系来更新子节点与爷爷节点的关系,而在集合
合并时需要根据x与y、x与pre[x],y与pre[y]的关系来更新fy与fx的关系,如何推导这个更新的关系表达式是个难理解的地方,有两种方法:枚举各种情
况,推导出规律来,另一种方法是利用矢量计算来推导
如果这道题不能理解的话,可以先去看 POJ 1703 Find them, Catch them,比较容易理解些`
#include<stdio.h>
const int MAXN=50005;
int pre[MAXN],re[MAXN];
int n,k;
int ans;
void init()
{
for(int i=1;i<=n;i++){
pre[i]=i;
re[i]=0;
}
}
int find(int x)
{
if(x!=pre[x]){
int t=pre[x];
pre[x]=find(pre[x]);
re[x]=(re[x]+re[t])%3;//路径压缩时更新与爷爷节点的关系
}
return pre[x];
}
void merge(int x,int y,int d)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy){ //说明x与y的关系不确定
pre[fy]=fx;
re[fy]=(d-1+re[x]-re[y]+3)%3;//合并时更新fy与fx的关系,注意x与y的关系为d-1。还要加一个3,避免负数的情况
}else{
if((re[y]-re[x]+3)%3!=d-1) ans++; //fx=fy说明x与y已经在同一棵树里,它们的关系已知,比较是否与题目给出的关系一致
}
}
int main(void)
{
scanf("%d%d",&n,&k);
int d,x,y;
ans=0;
init();
while(k--){
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n) {ans++;continue;}
if(d==2&&x==y){ans++;continue;}
merge(x,y,d);
}
printf("%d\n",ans);
return 0;
}