文章目录
1. 并查集+维护到根的距离
并查集,多维护一个数组 d
记录当前节点到父节点的距离,初始化为 0,即自己到自己的距离为 0。当前节点到所处集合的 root
节点距离在模 3 意义下只会出现三种情况:0,1,2
,我们将这三种情况分别指代为与 root
节点为 同类、吃根节点、被根节点吃。
路径压缩:递归找到当前点的 root
节点,这一步递归操作并查集都一样,但是需要注意,递归下去如果不对 p[x]
进行记录,那么在后面更新 d[x]
的时候就全是 root
节点的结果了,所以需要提前记录下 p[x]
的当前值,便于回溯求解 d[x]
。
判断 x
和 y
是否同类的真假话关系:
- 首先判断
x
和y
是否在同一集合里,即到root
节点距离是否相同- 如果相同,那么只需要再判断到
root
集合的距离是否相等即可,距离不等则为假话 - 如果不同,说明
x
和y
还没有关系,则进行合并,放入同一集合。这里采用并查集的合并即可,但是需要注意更新d[x]
数组,更新方式为 ( d [ x ] + ? − d [ y ] ) % 3 = 0 (d[x] + ? - d[y]) \%3 = 0 (d[x]+?−d[y])%3=0, ? ? ? 表示两个根节点之间的距离,那么移项可得 ? = d [ y ] − d [ x ] ?=d[y] - d[x] ?=d[y]−d[x],即 d [ p x ] = d [ y ] − d [ x ] d[px] = d[y] - d[x] d[px]=d[y]−d[x], d [ p x ] d[px] d[px] 是 d [ x ] d[x] d[x] 的root
节点,现在存放的是到 d [ p y ] d[py] d[py] 的距离,py
即是px
集合的新root
节点
- 如果相同,那么只需要再判断到
同理可分析x
吃 y
的真假话关系:
- 首先明确一点,
x
吃y
的话,x
到根的距离应该比y
到根的距离在模 3 意义下多 1 - 再判断
x
和y
是否在同一集合里,即到root
节点距离是否相同- 如果相同,判断 ( d [ x ] − d [ y ] − 1 ) % 3 (d[x] - d[y] - 1)\% 3 (d[x]−d[y]−1)%3 结果是否为 0 即可
- 如果不同,进行合并。更新方式为 ( d [ x ] + ? − d [ y ] − 1 ) % 3 (d[x] + ? - d[y] - 1) \%3 (d[x]+?−d[y]−1)%3 ,即 d [ p x ] = d [ y ] + 1 − d [ x ] d[px] = d[y] + 1 - d[x] d[px]=d[y]+1−d[x]
非注释代码:
#include <iostream>
using namespace std;
const int N = 5e5+5;
int n, m;
int p[N], d[N];
int find(int x) {
if (p[x] != x) {
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) p[i] = i;
int res = 0;
while (m --) {
int t, x, y;
cin >> t >> x >> y;
if (x > n || y > n) res ++;
else {
int px = find(x), py = find(y);
if (t == 1) {
if (px == py && (d[x] - d[y]) % 3) res ++;
else if (px != py) {
p[px] = py;
d[px] = d[y] - d[x];
}
}
else {
if (px == py && (d[x] - d[y] - 1) % 3) res ++;
else if (px != py) {
p[px] = py;
d[px] = d[y] + 1 - d[x];
}
}
}
}
cout << res << endl;
return 0;
}
带注释代码:
#include <iostream>
using namespace std;
const int N = 5e5+5;
int n, m;
int p[N], d[N]; // p为并查集存放父节点位置,d存放到父节点的距离
int find(int x) {
if (p[x] != x) {
int t = find(p[x]); // 在这里find(p[x])会吧p[x]直接搞成根节点,所以需要提前保存回溯
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) p[i] = i;
int res = 0;
while (m --) {
int t, x, y;
cin >> t >> x >> y;
if (x > n || y > n) res ++;
else {
int px = find(x), py = find(y); // 先找到x,y的根节点
if (t == 1) { // 如果两者是同类
// x,y已经在一颗树上了,x,y到根节点距离模3不同,则不为同类,为假话
if (px == py && (d[x] - d[y]) % 3) res ++;
else if (px != py) { // 不在一个集合里,执行两个集合合并的操作
// 记px到py为合并之间的距离为?
// 记x到px距离为dx,y到py的距离为dy
// 则若是同类,应该有(dx+?-dy)%3=0
// 那么 ? 就等于 dy-dx
p[px] = py;
d[px] = d[y] - d[x];
}
} else { // x吃y的话,那么x的距离应该比y多1
if (px == py && (d[x] - d[y] - 1) % 3) res ++;
else if (px != py) {
p[px] = py;
d[px] = d[y] + 1 - d[x];
}
}
}
}
cout << res << endl;
return 0;
}