题目描述
动物王国中有三类动物 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 句话有的是真
的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
• 当前的话与前面的某些真的话冲突,就是假话
• 当前的话中 X 或 Y 比 N 大,就是假话
• 当前的话表示 X 吃 X,就是假话
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
输入
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出
一行,一个整数,表示假话的总数。
输入样例
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例
3
说明
1 ≤ N ≤ 5 ∗ 10^4
1 ≤ K ≤ 10^5
对7句话的分析 100 7
1 101 1 假话
2 1 2 真话
2 2 3 真话
2 3 3 假话
1 1 3 假话
2 3 1 真话
1 5 5 真话
思路:
总体思想就是把并查集的空间开3倍当作扩展域,然后存3大类东西,同类域,吃域,被吃域。然后用并差距维护即可
存3倍扩展域的解释:
x1=find(x); 同类
x2=find(x+n); 吃域
x3=find(x+n*2); 被吃域
y1=find(y); 同类
y2=find(y+n); 吃域
y3=find(y+n*2); 被吃域
有了这样的分区域之后:
对于第二种谎话判断不提,输入的时候随手一分析就能看出
对于第一种谎话,如果y吃x或x吃y 因为同类不能吃所以为假话 否则x与y便是同类,即同类域,吃域,被吃域都相同
对于第三种谎话,如果x,y为同类或y吃x所以为假话,否则x的同类等于y被吃域的x,x的吃域等于y的同类,x的被吃域等于y所吃的z(因为就只有3类动物)
扩展域并查集写起来很顺手,只要你分析合理,虽然扩展域并查集提高了对空间的要求(每一个N把一种信息存到一个区块内分开储存),但是仍然是非常优秀的并查集(连merge都不需要专门想一想,只需要将扩展域的关系联系起来即可2333)
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ri register int
typedef long long LL;
using namespace std;
const int sz = 5000000;
inline void rd(int &x){
x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
if(f) x*=-1;
}
inline void we(int x){
if(x<0) putchar('-'),x*=-1;
if(x/10) we(x/10);
putchar(x%10+'0');
}
int fa[sz],ans;
int n,k,d,x,y;
int find(int x){
return x==fa[x]?x:x=find(fa[x]);
}
int main()
{
rd(n),rd(k);
for(ri i=1;i<=k*3;++i) fa[i]=i;
for(ri i=1;i<=k;++i)
{
rd(d),rd(x),rd(y);
if(x>n||y>n){
ans++;
continue;
}
int x1=find(x),x2=find(x+n),x3=find(x+n*2);
int y1=find(y),y2=find(y+n),y3=find(y+n*2);
if(d==1)
{
if(x1==y2||x2==y1) ans++;
else
fa[x1]=y1,fa[x2]=y2,fa[x3]=y3;
}
if(d==2)
{
if(x1==y1||x1==y2) ans++;
else
fa[x1]=y3,fa[x2]=y1,fa[x3]=y2;
}
}
we(ans);
return 0;
}
另一种版本的扩展域并查集
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int sz = 100010;
int ans,n,k,t,x,y;
int fa[sz<<2],rak[sz<<2];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
x = find(x);
y = find(y);
if(rak[x] > rak[y])
swap(x,y);
if(rak[x] == rak[y])
rak[y] ++;
fa[x] = y;
}
bool same(int x,int y){
return find(x) == find(y);
}
bool can(int x)
{
return (x >= 1 && x <= n);
}
int main()
{
scanf("%d%d",&n,&t);
for(int i=1;i<=3*n+1;i++) fa[i] = i;
for(int i = 1;i <= t;i ++)
{
scanf("%d%d%d",&k,&x,&y);
if(!can(x)||!can(y)){ans ++;continue;}
if(k == 1)
{
if(same(x,y + n) || same(y + 2 * n,x))
{
ans ++;
continue;
}
else
{
merge(x,y);
merge(x + n,y + n);
merge(x + 2 * n,y + 2 * n);
}
}
if(k == 2)
{
if(same(x,y) || same(x,y + 2 * n))
{
ans ++;
continue;
}
else
{
merge(x,y + n);
merge(x + n,y + 2 * n);
merge(x + 2 * n,y);
}
}
}
printf("%d\n",ans);
return 0;
}
其实这也是道加权并查集的好题,真是好题好题