带权并查集 NOI 2001 食物链

这题之前就做过,结果这次在 Coursera 上遇到又没做出来,,,
对并查集利用的思路真的很妙,所以还是记录一下
其实这题有两种方法,种类并查集 (开三倍大小的并查集) 与带权并查集
带权并查集的空间显然更优越,而且能扩展到物种食物链环长度为 \(n\) 的情况,所以这里主要介绍一下这种方法
首先这题并查集的特征很明显,问题就在于 \(a\)\(b\) 的关系不好描述

观察一条食物链 $a < b < c < d < e < f ... $ \(a\)\(d\), \(b\)\(e\), \(c\)\(f\)... 在食物链上每两个距离为 \(3\) 的动物必然是同一物种
根据这个特征,我们为并查集上的每一个点设置一个权值 \(w\) 代表该点到父亲结点 \(f_x\) 的距离取模 \(3\)

这样,在查询两个结点的关系时,若它们在同一连通块,我们只需要比较它们到并查集根节点之间的距离就行了(若两结点在同一连通块,路径压缩后两结点必有相同的父亲:连通块根节点)
例如 \(w\) 差值为 \(0\) 时(两结点到根距离相等)它们一定属同一物种

若两结点不在同一连通块,我们根据给出的信息对它们所在的连通块进行合并
接下来是在连通块合并时与寻根时 \(w\) 的更新操作
\(x\) 所在的连通块与 \(y\) 所在的连通块合并时,找到它们的根 \(f_x\)\(f_y\),并将 \(f_x\) 的父亲设为 \(f_y\)
在纸上画个图找向量关系:

  • \(x\)\(f_x\) 的距离为 \(w_x\)
  • \(y\)\(f_y\) 的距离为 \(w_y\)
  • \(x\)\(y\) 的距离为 \(0\) (若两者为同一物种) 或 \(1\) (若 \(x\)\(y\))

那么 \(f_x\)\(f_y\) 的距离 \(w_{f_x}\) 即为 \(w_y - w_x + 0/1\)

另外值得一提的是路径压缩时,结点父亲改变带来的对 \(w\) 的更新
原来的 \(w_x\) 是相对于自己父亲 \(w_{fx}\) 的距离,路径压缩之后则是对于新父亲 \(w_{root_x}\) 的距离
具体的更新方式是先储存原父亲,待递归后原父亲的 \(w_{f_x}\) 被更新,此时我们只需要将自己到原父亲的距离 \(w_x\) 加上更新后原父亲到新父亲的距离 \(w_{f_x}\) 即可得到自己到新父亲的距离,也即更新后的 \(w_x\)
接下来是代码:

#include <iostream>

using namespace std;

const int MAX_N = 1e5 + 10;

int fa[MAX_N], w[MAX_N];

inline int get(int x) {
	if (x == fa[x])		return x;
	int tmp = fa[x];
	fa[x] = get(fa[x]);
	w[x] = (w[x] + w[tmp] + 3) % 3;
	return fa[x];
}

int main() {

	int n, k;
	cin >> n >> k;

	for (int i = 1; i <= n; ++i)	fa[i] = i;

	int ans = 0;
	while (k--) {
		int op, x, y;
		cin >> op >> x >> y;
		if (x > n || y > n || (op == 2 && x == y)) {
			++ans;
			continue;
		}
		op -= 1;
		if (get(x) == get(y)) {
			if ((w[x] - w[y] + 3) % 3 != op)	++ans;
		} else {
			int fa_x = get(x), fa_y = get(y);
			fa[fa_x] = fa_y;
			w[fa_x] = (w[y] - w[x] + op + 3) % 3;
		}
	}

	cout << ans << endl;

	return 0;
}
posted @ 2022-05-30 11:08  四季夏目天下第一  阅读(35)  评论(0编辑  收藏  举报