【问题描述】
动物王国中有三类动物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),输出假话的总数。
【输入格式】
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
【输出格式】
只有一个整数,表示假话的数目。
【输入输出样例】
输入:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出:
3
【算法分析】
本文总结的“种类并查集”的思想,主要来自于博客:
【带权并查集 He 种类并查集 详详详详详详详hhh】经典例题_L_X_-CSDN博客
POJ - 1182: 食物链 (并查集)_Jerry的博客-CSDN博客_poj1182
种类并查集的find()、merge()函数与经典并查集的find()、merge()函数完全一样。
种类并查集与经典并查集的区别,仅仅在于种类并查集在初始化时需要开k倍于结点数的空间。这里的k,是指在具体问题中将结点分成的种类,且一般较小(≤3)。这也就是种类并查集的核心思想,即:若设k为状态数,n为结点数,则种类并查集需开k*n的空间。相应的,结点x的k个状态分别表示为x、x+n、x+2n、……、x+(k-1)n。
如在问题“POJ 1182:食物链”中,每个动物x的信息(即此问题中的结点信息),用三个状态来表示:x、x的食物、x的天敌。若设动物总数为n,则依据种类并查集的思想需开3n空间。若用A、B、C分别表示x、x的食物、x的天敌这3个状态,则动物x的状态就形式化为如下结点:
A:x → A:[1,n]
B:x+n → B:[n+1,2n]
C:x+2n → C:[2n+1,3n]
种类并查集和经典并查集一样,认为如果两个结点是连通的,则它们属于一类。比如:
如果有x+n和y连通,即x的食物和y连通,表示x的食物和y是同类。等价于关系“x吃y”。
如果有x+2*n和y连通,即x的天敌和y连通,表示x的天敌和y是同类。等价于关系“x被y吃”。
所以,在问题“POJ 1182:食物链”中,如果判断“1 x y”,即判断x、y是不是同类,等价于判断:
• 是不是有x吃y,即判断same(x+n,y)?
• 是不是有y吃x,即判断same(x+2n,y)?
如果都没有,说明x、y是同类,那么需合并(x,y)、(x+n,y+n)、(x+2n,y+2n)
所以,在问题“POJ 1182:食物链”中,如果判断“2 x y”,即判断x是不是吃y,等价于判断:
• 是不是有y吃x,即判断same(x+2n,y)?
• 是不是有x、y是同类,即判断same(x,y)?
如果都没有,说明x吃y,那么合并(x+n,y)、(x+2n,y+n)、(x,y+2n)
注意:
虽然种类并查集容易理解和模拟,但是由于种类并查集在初始化时需要开k倍于结点数n的空间用于表示n个不同结点的各自k个状态,所以种类并查集在解决空间受限及状态过多的题目时不适用。因此,需将种类并查集解决方案转换为带权并查集解决方案,因为此时带权并查集更占优势。
带权并查集是经典并查集的进阶版本,它是在经典并查集的基础上添加了一个value[]数组,表示权值(本质上是某种关系的量化)。这个value[]数组可以记录很多种东西,从而使得带权并查集功能更加强大。经典并查集只能判断两个元素是否在同一个集合中,而带权并查集可以维护集合元素之间的关系。带权并查集中结点与结点之间的关系可用数学上向量的知识分析计算。
【算法代码】
#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
int pre[maxn*3]; //If having n categories, then expand space by n times
int n,k,ans;
int read() { //fast read
int x=0,f=1;
char c=getchar();
while(c<'0' || c>'9') { //!isdigit(c)
if(c=='-') f=-1;
c=getchar();
}
while(c>='0' && c<='9') { //isdigit(c)
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
int find(int x) {
if(x!=pre[x]) pre[x]=find(pre[x]);
return pre[x];
}
void merge(int x,int y) {
if(find(x)!=find(y)) pre[find(x)]=find(y);
}
bool same(int x, int y) { //very important
return find(x)==find(y);
}
void init() {
for(int i=0; i<=n*3; i++) pre[i]=i;
}
int main() {
n=read();
k=read();
init();
for(int i=0; i<k; i++) {
int d,x,y;
d=read();
x=read();
y=read();
if(x<=0 || x>n || y<=0 || y>n) ans++;
else {
if(d==1) { //Is x the same as y?
if(same(x+n,y) || same(x+2*n,y)) //x eats y, or y eats x
ans++;
else {
merge(x,y);
merge(x+n,y+n);
merge(x+2*n,y+2*n);
}
} else if(d==2) { //Determine whether x eats y?
if(same(x,y) || same(x+2*n,y)) //x is the same as y, or y eats x
ans++;
else {
merge(x+n,y);
merge(x+2*n,y+n);
merge(x,y+2*n);
}
}
}
}
printf("%d\n", ans);
return 0;
}
/*
in:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
out:
3
*/
【参考文献】
https://www.luogu.com.cn/problem/P2024
https://www.acwing.com/problem/content/description/242/
https://blog.csdn.net/weixin_44049850/article/details/105312801
https://blog.csdn.net/qq_45034708/article/details/105223573
https://www.cnblogs.com/-Ackerman/p/11780355.html
https://blog.csdn.net/Richard_for_OI/article/details/79322782
https://blog.csdn.net/lisong_jerry/article/details/80029967
https://blog.csdn.net/hnjzsyjyj/article/details/120131534
https://blog.csdn.net/hnjzsyjyj/article/details/120120591