题目:
题意:
输入若干句话,请求我们判断一共有几句是假话
分析:
这一题,典型的并查集例题升级版,难度还是有的,但一旦做出来,就会发现自己对并查集的熟练度增加了不止一倍,下面小编就来为大家分析一下这一题:首先,在题目中判定一句话是假话有3个条件:
• 当前的话与前面的某些真的话冲突,就是假话
• 当前的话中 X 或 Y 比 N 大,就是假话
• 当前的话表示 X 吃 X,就是假话
根据如此,我们可以一边读入,一边处理。后面两个两个条件是极好判断的,但难点就在于第一点,这时候,我们就引入一个全新的并查集概念:补集法,而这个概念的主要实现就是将数组开到x*n(x为任意一数)的范围。在这一题中,我们之所以要用到补集法是因为题目中出现了三种关系:A→B→C→A,与其说是一个食物链,倒不如是一个食物环,将食物链转化到了这一步,我们就不难看出每种动物都有三种关系:同类,猎物以及天敌。而我们使用补集法,就可以将同类储存到f[i]中,而猎物则放到f[i+n]中,同理,天敌就放到f[i+n*2]中。既然如此,那我们每读入两个动物,就看下他们的祖先是否符合当前的描述,如果不符合,那么累加,进入下一次循环,如果符合的话就将这两种动物进行合并。更具体的操作请看代码:
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
LL d=0,f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
return d*f;
}
int f[150001];
int father(int i)//找i的祖先
{
return f[i]==i? i:f[i]=father(f[i]);//加速递归
}
void hb(int x,int y)//合并两个集合
{
int r1=father(f[x]),r2=father(f[y]);
f[r1]=r2;
}
int main()
{
int n,m;
n=read();m=read();
int ans=0;//ans为统计谎话数
int a,b,c;
for(int i=1;i<=n*3;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
a=read();b=read();c=read();
if(b>n||c>n) {ans++; continue;}//先将两个简单的条件判断了
if(b==c&&a==2) {ans++;continue;}
if(a==1)//当我们可能是同类时:
{
if(father(b+n)==father(c)||father(b+n*2)==father(c)) {ans++;continue;}//如果你是我的猎物或天敌,那你肯定不是我的同类,所以ans+1
hb(b,c);hb(b+n,c+n);hb(b+n*2,c+n*2);//如果真是同类,那么我的同类就是你的同类,我的猎物就是你的猎物,我的天敌也是你的天敌
}
else
if(a==2)//当你可能是我的猎物时:
{
if(father(b)==father(c)||father(b+n*2)==father(c)) {ans++;continue;}//如果我们是同类或你是我的天敌,那么这句话就是假的
hb(b,c+n*2);hb(b+n,c);hb(b+n*2,c+n);//如果你真是我的猎物,那么我的同类就是你的天敌,我的猎物就是你的同类,我的天敌就是你的猎物
}
}
printf("%d",ans);//输出谎话数
return 0;
}