P2024 (NOI2001)食物链
题目描述
动物王国中有三类动物 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 句话,输出假话的总数。
输入格式
从 eat.in 中输入数据
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式
输出到 eat.out 中
一行,一个整数,表示假话的总数。
输入输出样例
输入 #1 复制
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出 #1 复制
3
说明/提示
1 ≤ N ≤ 5 ∗ 10^4
1 ≤ K ≤ 10^5
种类并查集的意义
并查集能维护连通性、传递性,通俗地说,亲戚的亲戚是亲戚。
然而当我们需要维护一些对立关系,比如 敌人的敌人是朋友 时,正常的并查集就很难满足我们的需求。
这时,种类并查集就诞生了。
常见的做法是将原并查集扩大一倍规模,并划分为两个种类。
在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友这个含义。
考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人 这个含义了。
按照并查集美妙的 传递性,我们就能具体知道某两个元素到底是 敌人 还是 朋友 了。
种类并查集思路
就是用3倍的并查积的存各种动物的关系
一倍存本身,二倍存猎物,三倍存天敌
唯一容易忽略的点就是:自己的猎物的猎物 就是自己的天敌
那么我们每次只要维护三个并查积的关系就可以了
种类并查集题解
#include
using namespace std;
const int MAXN=2e5+10;
int fa[MAXN];
int n,k,opt,x,y;
int read() //读入优化
{
char ch;
int n=0;
ch=getchar();
while(ch<'0'||ch>'9'){ch=getchar(); }
while(ch>='0'&&ch<='9'){
n=((n<<3)+(n<<1)+(ch&15));
ch=getchar();
}
return n;
}
int find(int x) //查询
{
while(x!=fa[x])
{
x=fa[x];
}
return x;
}
int main()
{
int ans=0;
n=read();
k=read();
for(int i=1;i<=3*n;i++)
fa[i]=i;
//对于每种生物:设 x 为本身,x+n 为猎物,x+2*n 为天敌
for( ;k ;k--)
{
opt=read();
x=read();
y=read();
if(x>n||y>n){ // 不属于该食物链显然为假
ans++;
continue;
}
if(opt==1)
{
if(find(x+n)==find(y)||find(x+2*n)==find(y)){
ans++;
continue;
}
//如果1是2的天敌或猎物,显然为谎言
fa[find(x)]=find(y);
fa[find(x+n)]=find(y+n);
fa[find(x+2*n)]=find(y+2*n);
//如果为真,那么1的同类和2的同类,1的猎物是2的猎物,1的天敌是2的天敌
}
if(opt==2)
{
if(find(x)==find(y)||find(x+2*n)==find(y)){
ans++;
continue;
}
//如果1是2的同类或猎物,显然为谎言
fa[find(x)]=find(y+2*n);
fa[find(x+n)]=find(y);
fa[find(x+2*n)]=find(y+n);
//如果为真,那么1的同类是2的天敌,1的猎物是2的同类,1的天敌是2的猎物
}
}
cout<
带权并查集的意义
上网大概搜了下,对带权并查集的诠释是这样的:
在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。
也就是说,权值代表着当前节点与父节点的某种关系(即使路径压缩了也是这样),通过两者关系,也可以将同一棵树下两个节点的关系表示出来。
带权并查集思路
由题意得,动物一共只有A,B,C三种,也就是说只要确定了一种动物的种类和他们的关系(即权值),其他的动物的种类也就知道了。
我们用re数组表示编号i与父亲节点的权值关系,由于只有三种动物,所以权值也只有三种:0–>同种动物,1–>捕食关系,2–>被捕食关系,转移时便可以采用对3取模来实现。(初始化为0,即自己与自己为同种动物)
带权并查集题解
#include
#include
using namespace std;
const int MAXN=2e5+10;
int fa[MAXN],d[MAXN]; //0-->同种动物,1-->捕食关系,2-->被捕食关系。
int read()
{
int n=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
n=((n<<1)+(n<<3)+(ch&15));
ch=getchar();
}
return n;
}
int find(int x)
{
if(x==fa[x])
{
d[x]=0;
return x;
}
int f=find(fa[x]);
d[x]=(d[x]+d[fa[x]])%3;
fa[x]=f;
return f;
}
int main()
{
int n,k,opt,x,y;
int ans=0;
n=read();
k=read();
// cout<0 ; k--)
{
opt=read();
x=read();
y=read();
if(x>n||y>n||(opt==2&&x==y)){
ans++;
continue;
}
if(opt==1)
{
int f_x=find(x);
int f_y=find(y);
if(f_x==f_y&&d[x]!=d[y]){ //判断是否在同一棵树及两者是否为同种动物。
ans++;
continue;
}
else if(f_x!=f_y){ //合并
fa[f_x]=f_y;
d[f_x]=(3-d[x]+d[y])%3;
}
}
if(opt==2)
{
int f_x=find(x);
int f_y=find(y);
if(f_x==f_y&&(3+d[x]-d[y])%3!=1) //用两个节点与父亲的关系推出两者关系
{
ans++;
continue;
}
else if(f_x!=f_y){ //合并
fa[f_x]=f_y;
d[f_x]=(3-d[x]+d[y]+1)%3;
}
}
}
cout<