1. 程式人生 > 實用技巧 >題解 P5089 【[eJOI2018]元素週期表】

題解 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;
}