描述
三种动物 A吃B, B吃C, C吃A.
1, a, b 表示a,b同类
2, a, b 表示a吃b
判断假话个数
分析
- NOIp前看过, 当时不会, 现在理解起来也有点抽象.
- 按权值合并的并查集
- 能确定关系的 x, y 的find到的祖先相同.
- pa[x] 是 x 的祖先, r[x] 是 x 到祖先的距离, 有三种, 0, 1, 2. 0 表示和祖先同类, 1表示可以吃祖先, 2表示被吃. r[find(x)] 一定为 0 我用 assert 验证了一下.
- 查找时先记录下 x 当前的祖先 px, 可知当 x 和 px 最初确定关系的时候 r[px] 一定为0. 而递归 find(px) 之后 r[px] 的数值就是 px 到现在的祖先的距离, x 和 px 现在的祖先是相同的, 那么 x 到现在祖先的距离就是 x 到原来 px 的距离加上 px 到现在祖先的距离.
- 合并 x, y 时, 先px = find(x), py = find(y). 比如要把 pa[px] 设成 py, 那 py 是不会变了, 考虑 px 的秩怎么变. 如果合并后要求 x, y 同类, 那么 x 到 py 的距离应当等于此时的 r[y], 那么 r[x] + r[px] == r[y], 所以 r[px] = r[y] - r[x]. 这样在路径压缩后新的 r[x] 就等于 r[y] 了.
#include
#include
using namespace std;
const int maxn = 50000 + 10;
int pa[maxn], r[maxn];
int find(int x) {
if(pa[x] == x) return x;
int px = pa[x];
pa[x] = find(px);
r[x] = (r[px] + r[x]) % 3;
return pa[x];
}
int main() {
int n, m, ans = 0;
scanf("%d %d", &n, &m);
for(int x = 1; x <= n; x++) pa[x] = x;
for(int i = 1; i <= m; i++) {
int k, x, y, px, py;
scanf("%d %d %d", &k, &x, &y);
if(x > n || y > n) { ans++; continue; }
px = find(x); py = find(y);
assert(r[px] == 0);
assert(r[py] == 0);
if(k == 1) {
if(px == py) { if(r[x] != r[y]) ans++; }
else pa[px] = py, r[px] = (r[y]-r[x]+3) % 3;
} else {
if(px == py) { if(r[x] != (r[y]+1) % 3) ans++; }
else pa[px] = py, r[px] = (r[y]-r[x]+4) % 3;
}
}
printf("%d\n", ans);
return 0;
}