题意
这是一道非常经典的并查集题目,或许应该叫种类并查集更好。题目意思很好理解,问题在于怎么去判断每一句话是真话还是假话还是不确定。
思路
-
x > n || y > n那肯定是假话不用加以其他的判断就能的出来
-
d == 2 && x == y,既然x要吃掉y,那么x和y肯定不能相等啊,所以这也是一个假话。
-
然后就是判断现在说的这句话与前面的话是不是冲突,这也是这道题的核心所在。
-
引入概念一:s[x]数组代表x 的当前父节点是 s[x],切记是当前父节点
-
引入概念二:r[x]数组表示x 与 s[x]父节点的关系,r[x] = 0代表x 与 s[x]属于同类,r[x] = 1代表 x 可以吃掉 s[x],r[x] = 2代表 x会被 s[x]吃掉,如下图
现在问题就转化成如何在并查集中维护子节点和父节点的关系,并且能够轻而易举的知道两则的关系,大概有三种核心操作:路径压缩关系转化,同类判定,x吃y是否成立,节点合并。 -
路径压缩关系转化
路径压缩之前r[x]代表的是x 和 y的关系,压缩之后需要把r[x]转化为 x 和 z的关系,向量xz是要求的,向量xy = r[x],向量yz = r[y],可以推出压缩之后r[x] = (r[x] + r[y])%3,为什么要%3?假如r[x] 和 r[y]都为2,压缩之后应该是 1 而不是 4。 -
同类判定
只要r[x] = r[y]那么x 和 y就是同类,因为查询之前需要做路径压缩,压缩完之后x 和 y都是根节点的孩子,如果他们与根节点的关系一致那么他们就是同类,前提是他们在同一个根节点下。 -
x吃y是否成立
向量xy = 向量xz - 向量yz,进而转化为r[x] - r[y] == 1是否成立,有几个小细节,r[x] - r[y]可能出现负数,所以需要 + 3 处理然后整体%3,在判断是不是等于1,如果成立那么 x 吃 y这句话就是对的。
- 节点合并(最难的)
大概关系就在上面一张图里了,合并x 和 y就是需要合并 a 和 b;x 吃 y是d = 1,输入的d是2,x 和 y是同类 d = 0,输入的d = 1;所以两种操作都要减掉1。然后还是可能会出现负数的情况,所以老规矩 + 3 然后对3取余就是父节点a 和 b的关系。
所有的东西都在上面了,下面贴一份代码参考,如有不对的地方请指正。这道题还有最后一个坑点就是不能多组输入,多组输入就WA,不知道什么原因也不敢问。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50010;
int s[maxn];
int r[maxn];
void clear_set()
{
for(int i = 0;i < maxn;i++){
s[i] = i;
}
memset(r,0,sizeof(r));
}
int find_set(int x)
{
if(x != s[x]){
int f = s[x];
s[x] = find_set(s[x]); //路径压缩
r[x] = (r[x] + r[f])%3; //更新子节点与根节点的关系
return s[x];
}
return s[x];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m); //单组读入就能过,多组读入就WA
clear_set();
int ans = 0;
while(m--){
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x > n || y > n){ //假话2
ans++;
continue;
}
if(d == 2 && x == y){ //假话3
ans++;
continue;
}
int fx = find_set(x);
int fy = find_set(y);
if(fx != fy){
s[fx] = fy;
r[fx] = (r[y] - r[x] + (d-1) + 3)%3;
}
else{
if(d == 1 && r[x] != r[y]){ //假话1冲突
ans++;
}
if(d == 2 && ((3+r[x] - r[y]) % 3 != 1)){ //假话1冲突
ans++;
}
}
}
printf("%d\n",ans);
return 0;
}
愿你走出半生,归来仍是少年~