食物链
时间限制:
1000
ms | 内存限制:
65535
KB
难度:
5
-
描述
-
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
这道题可以算的上是很经典的并查集的题目了,刚开始做的时候也没思路啊,各种wa啊,后来参考了大神的思路http://blog.csdn.net/niushuai666/article/details/6981689,也看了好久,才把那个更新域搞懂。
这个题目,最重要的是处理关系域的更新,两个集合合并了之后,合并成一个集合,之间的关系就变了,就要更新他们之间的关系,其他的地方就和普通的并查集一样,这里才是并查集的本质吧。
#include <stdio.h>
#define MAXN 10000
struct node
{
int father;
int relation;//定义他们之间的关系
};
node s[MAXN];
int find(int x)//带动态压缩的查找
{
int temp;
if(x==s[x].father)
return x;
temp=s[x].father;
s[x].father=find(temp);
s[x].relation=(s[x].relation+s[temp].relation)%3;//关系域的更新,要找准他们之间的关系
return s[x].father;
}
int main()
{
int n,k,d,x,y,i,sum=0;
int root1,root2;
scanf("%d%d",&n,&k);
for(i=0;i<n;i++)//初始化
{
s[i].father=i;
s[i].relation=0;
}
for(i=0;i<k;i++)
{
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n)
{
sum++;
continue;
}
if(d==2&&x==y)
{
sum++;
continue;
}
root1=find(x);
root2=find(y);
if(root1!=root2)//合并操作
{
s[root2].father=root1;
s[root2].relation=(3+(d-1)+s[x].relation-s[y].relation)%3;//域的更新
}
else
{
if(d==1&&s[x].relation!=s[y].relation)
{
sum++;
continue;
}
if(d==2&&((3-s[x].relation+s[y].relation)%3!=d-1))
{
sum++;
continue;
}
}
}
printf("%d\n",sum);
return 0;
}
这是一种做法,看到挑战程序设计竞赛里面的有另一种做法。这里也可以用不带秩的合并(内存可以少一点)。
#include<stdio.h>
#include<string.h>
#define MAX 10000
int parent[MAX],rank[MAX];//父亲,树的高度
int n,k,cnt;
int find(int r)
{
int p=r;
while(p!=parent[p]) p=parent[p];
while(r!=p)
{int temp=parent[r];parent[r]=p;r=temp; }
return p;
}
void unionn(int r1,int r2)
{
r1=find(r1),r2=find(r2);
if(rank[r1]<rank[r2])
{parent[r1]=r2; }
else
{
parent[r2]=r1;
if(rank[r1]==rank[r2]) rank[r1]++;//因为r2的父节点变成了r1,深度+1
}
}
bool same(int a,int b)
{
return find(a)==find(b); //相等表示同一个集合
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
cnt=0;
for(int i=0;i<=3*n;i++)//这里赋值到3*n,一开始没注意到,然后结果也不对
{
parent[i]=i;
}
memset(rank,0,sizeof(rank));
for(int i=0;i<k;i++)
{
int d,r1,r2;
scanf("%d%d%d",&d,&r1,&r2);
if(r1>n||r2>n)
{ cnt++;continue; }
/*以下要做的,就是r1表示A种类,r1+n表示B,r1+2*n表示C ,r2同理。
*/
if(d==1)//同类
{
if(same(r1,r2+n)||same(r1,r2+2*n) )
/*这里是处理同类的2种情况,
假如r1(A种类)和r2(B种类)是同一个集合,那么这肯定是假话
假如r1(A种类)和r2(C种类)是同一个集合,也是假话
*/
cnt++;
else
{unionn(r1,r2);unionn(r1+n,r2+n);unionn(r1+2*n,r2+2*n); }
/*假如是真话,它们就是同类,分别合成一个集合,A与A,B与B,C与C */
}
else//x吃y
{
if(same(r1,r2)||same(r1,r2+2*n) )
/*这里是处理x吃y的2种情况,
假如r1(A种类)和r2(A种类)是同一个集合,不能同类相吃 ,是假话
假如r1(A种类)和r2(C种类)是同一个集合,也是假话 ,只有C种类吃A种类,A种类只能吃B种类。
*/
cnt++;
else
{unionn(r1,r2+n);unionn(r1+n,r2+2*n);unionn(r1+2*n,r2); }
/*假如是真话,那么就让x吃y的关系合成一个集合,A种类吃B,B吃C,C吃A */
}
}
printf("%d\n",cnt);
}
return 0;
}
感觉还是太菜了,有些细节还是处理的不好,理解跟自己能够完全做出来还是两码事啊。。。