题解 P5089 【[eJOI2018]元素周期表】

传送门
提供一个并查集写法

由题意可以得出一个性质,如果两个周期(即两行)中存在一列值都为一,那么这两行没有任何区别,换句话说,若其中的一行某一列的元素是存在的,另一行该列也可以通过反应来更新,所以我们可以用并查集将所有没有区别的行(即有相同列的行)都合并起来,此时得到了很多联通块和一些没有元素的列,没有元素的行。

更新前(第一行和第四行在第三列有重):

1 0 1 0 1 1 0 1 1 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 1 0 0 0

更新后:

1 0 1 0 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
1 0 1 0 1 1 1 1 1 1

机房把图床封了QwQ

考虑没有元素的列(如第二列和第四列):我们一定需要购买一个元素,先只购买一次,(因为后面把行都合并起来后,只要一列有一个元素,那么就可以更新到所有位置)这个操作是必须执行的,否则无法更新到该列。

考虑没有元素的行:和没有联通块的列一样,直接购买。

思考一个问题:买空行和空列的位置要求吗?

手动模拟一下可以发现如果空列买在空行和非空行效果是一样的,因为如果买在空行空列还是需要买一次将这个点和其他联通块连起来,与买在非空行需要的购买次数是一样的

考虑不同的联通块:显然最优的方案是在一个联通块的一列买一个元素,使得这一列在另一个联通块也存在,本质上就是把两个联通块合并起来(也就是前面的性质)。

我们可以得出答案:空列的个数+空行的个数+联通块的个数-1(因为x个联通快只需要合并x-1次)

代码很好写

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
inline int read () {
	int x = 0, f = 1; char ch = getchar();
	for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
	for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	return x * f;
}
const int maxn = 2e5 + 50;
int n, m, q;
struct Node {
	int x, y;
} node[maxn];
int fa[maxn], bel[maxn];
bool tongy[maxn], tongx[maxn], T[maxn];
int ans;
inline int find_root (int a) {
	return fa[a] == a ? a : fa[a] = find_root(fa[a]);
}
inline void merge (int a, int b) {
	fa[find_root(a)] = find_root(b);
}
int main () {
	n = read(), m = read(), q = read();
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= q; i++) {
		node[i].x = read(), node[i].y = read();
		tongy[node[i].y]++, tongx[node[i].x]++;
		if (bel[node[i].y] == 0) bel[node[i].y] = node[i].x;
		else merge (node[i].x, bel[node[i].y]);
	}
	for (int i = 1; i <= m; i++) {
		if (tongy[i] == false) ++ans;
	}
	for (int i = 1; i <= n; i++) {
		if (tongx[i] == 0) {
			ans++;
		} else if (T[find_root(i)] == false) {
			ans++, T[find_root(i)] = true;
		}
	}
	--ans;
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-11-23 20:26  Blancа  阅读(104)  评论(1编辑  收藏  举报
哈哈,你打开了控制台,是想要看看我的秘密吗?
莫挨老子!