传送门
这道题是典型的 带权并查集。
思想很巧妙,关键是怎么处理已有的事实来确定当前话的真假,关键是怎么存储已有事实,答案存储是在一颗颗边带权的树里,树是有向边,每条有向边上的权值表示上下结点的关系(也就是这两只动物的关系,到底是一样?还是A吃B?还是B吃A?)。如果当前话的两个动物属于不同的树,那么这句话一定为真的(因为这两个动物无法根据已有事实来链接起来关系)。当前话的两个动物只有属于同一颗树,我们才能有资格判断这句话是真的还是假的。
也就是说,每一棵树代表了一个独立的关系圈,如果查询的两个动物属于不同的关系圈,那么我们根本没有任何手段去判断真的假的,我们没有任何知识来推翻这句话,所以我们只能默认这句话是真的,为以后作准备。而且我们还要把这两个关系圈链接起来,使之成为一个关系圈。
现在,点明代码中涉及r[]
数组的三处操作。
r[x] = (r[x] + r[pre[x]]) % 3;
- 这句是在
find
函数里的,我理解的是,r[]
数组在赋值号左端时,代表当前节点与根节点的关系(此时当前节点的父节点一定或马上就是根节点!);r[]
数组在赋值号右端时,代表与父节点的关系(当然,父节点可能就是根节点)。
有点啰嗦,其实简单说,r[]
数组表示的就是和父节点的关系,但是它被赋值时父节点一定是根节点(没有例外!)
r[]
数组的具体内容表示什么意思呢?0
代表和父节点是同一种生物,1
代表被父节点吃,2
代表吃父节点。
现在回到这句代码上,因为并查集的压缩规则,现在x
要被链到根节点上,所以它的r[]
也该更新了,它的旧的r[]
表示和父节点的关系(但是现在父节点可能已经不是根节点了(因为在merge
函数里改动)),在这句代码之前,它的父节点的递归调用已返回,父节点已经被链到根节点上且其r[]
值已被更新。所以就是根据与父节点的关系以及父节点和根节点的关系来确定当前节点与根节点的关系,然后紧接着下一句当前节点被链到根节点上。
再提一下这个递归,首先是从最底层当前查询节点出发,逐步向上,逐步递归调用,逐步压栈,直到找到根节点,然后开始递归返回,开始弹栈,开始向下,在返回的途中遇到的每一个点都把它链到根节点上(函数返回值就专门返回这个根)(返回时从上向下的第三个点开始真正有效)。
至于这句的%3
这个式子是怎么来的,自己写真值表找规律总结之,反正也没几种组合。
if (d == 1 && r[x] != r[y]) counter++;
if (d == 2 && (r[x] != 1 || r[y] != 2) && (r[x] != 0 || r[y] != 1) && (r[x] != 2 || r[y] != 0)) counter++;
- 这两句不会同时发生,所以没写
else if
,第二句的条件看起来有点晕,其实可以逆向求条件再取反。
if (r[y] == 0) r[fy] = r[x];
else if (r[y] == 1) r[fy] = (r[x] + 2) % 3; // fy 与 y 可能一样 必须 else if
else if (r[y] == 2) r[fy] = (r[x] + 1) % 3;
- 这三句是在
fy
子树被链到fx
上且d=1
时的发生的,d=1
意味x
和y
是同一种动物,再枚举r[y]
的值,fy
和fx
的关系就只剩由r[x]
这个参数了来确定了,还是写真值表,做的时候得画图。
注意当d=2
时两种大情况的式子不一样,要重新推导,因为d=2
是有偏向性的,指x
吃y
。 - 想一想为什么不怕
fx,fy
和x,y
一样?设置r[]
数组的同类关系为0
以及r[]
初始化为0
是不是就有这种考量?
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 50005;
int N, K;
int pre[MAXN];
int r[MAXN];
int counter;
int find(int x) // 和hdoj 1829差不多,带权并查集
{
if (pre[x] < 0) return x;
int t = find(pre[x]);
r[x] = (r[x] + r[pre[x]]) % 3; //1
return pre[x] = t;
}
void merge(int d, int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx == fy)
{
if (d == 1 && r[x] != r[y]) counter++; //2
if (d == 2 && (r[x] != 1 || r[y] != 2) && (r[x] != 0 || r[y] != 1) && (r[x] != 2 || r[y] != 0)) counter++;
return;
}
if (pre[fx] <= pre[fy])
{
pre[fx] += pre[fy];
pre[fy] = fx;
if (d == 1)
{
if (r[y] == 0) r[fy] = r[x]; //3
else if (r[y] == 1) r[fy] = (r[x] + 2) % 3; // fy 与 y 可能一样 必须 else if
else if (r[y] == 2) r[fy] = (r[x] + 1) % 3;
}
else
{
if (r[y] == 0) r[fy] = (r[x] + 1) % 3;
else if (r[y] == 1) r[fy] = r[x];
else if (r[y] == 2) r[fy] = (r[x] + 2) % 3;
}
}
else
{
//
pre[fy] += pre[fx];
pre[fx] = fy;
if (d == 1)
{
if (r[x] == 0) r[fx] = r[y];
else if (r[x] == 1) r[fx] = (r[y] + 2) % 3;
else if (r[x] == 2) r[fx] = (r[y] + 1) % 3;
}
else
{
if (r[x] == 0) r[fx] = (r[y] + 2) % 3;
else if (r[x] == 1) r[fx] = (r[y] + 1) % 3;
else if (r[x] == 2) r[fx] = r[y];
}
//
}
}
int main()
{
counter = 0;
memset(pre, -1, sizeof pre);
memset(r, 0, sizeof r);
int d, x, y;
scanf("%d%d", &N, &K);
for (; K--;)
{
scanf("%d%d%d", &d, &x, &y);
if (x > N || y > N || d == 2 && x == y)
{
counter++;
continue;
}
merge(d, x, y);
}
printf("%d\n", counter);
return 0;
}
//pre[fy] += pre[fx];
//pre[fx] = fy;
//if (d == 1)
//{
// if (r[y] == 0) r[fx] = r[x];
// else if (r[y] == 1) r[fx] = (r[x] + 2) % 3;
// else if (r[y] == 2) r[fx] = (r[x] + 1) % 3;
//}
//else
//{
// if (r[y] == 0) r[fx] = (r[x] + 1) % 3;
// else if (r[y] == 1) r[fx] = r[x];
// else if (r[y] == 2) r[fx] = (r[x] + 2) % 3;
//}
//if (r[fx]) r[fx] = r[fx] ^ 3; // 得出对称的 关系式 要谨慎
//
// // 其实这样写没啥意思,不如上面把x y反过来,直接写好
// // 异或运算,^(0011),和0异或不变,和1异或取反