P9869 [NOIP2023] 三值邏輯

Svemit發表於2024-05-08

賽時做法。

欽定 \(T\) 為 1,\(F\) 為 -1,\(U\) 為 0。

發現每個值都有兩個狀態,它本身及其取反。

\(m\) 個操作完後我們對每個值的狀態一共有三種情況:

  1. 沒有對這個值進行操作,不知曉任何資訊。

  2. 這個值與另外一個值有關,能從另外一個值推出當前值。

  3. 已經確定是 \(T/F/U\)

考慮定義陣列 \(ok_x\) 表示 \(x\) 這個值有沒有被確定,如果被確定了則將其存在 \(val_x\) 中。

對於第二種情況,定義陣列 \(p_x\)\(sig_x\) 表示 \(x\) 的值與 \(p_x\) 的值有關,\(sig_x = 1/-1\) 表示 \(x\) 是 / 否需要對 \(p_x\) 取反。

此時很容易處理出輸入部分的程式碼。

while (m --) {
	char op;
	int x, y;
	cin >> op;
	if (op == '+') {
		cin >> x >> y;
		if (ok[y]) {
			ok[x] = true;
			val[x] = val[y];
		} else {
			ok[x] = false;
			p[x] = p[y];
			sig[x] = sig[y];
		}
	} else if (op == '-') {
		cin >> x >> y;
		if (ok[y]) {
			ok[x] = true;
			val[x] = -val[y];
		} else {
			ok[x] = false;
			p[x] = p[y];
			sig[x] = -sig[y];
		}
	} else {
		cin >> x;
		ok[x] = true;
		if (op == 'U') {
			val[x] = 0;
		} else if (op == 'T') {
		val[x] = 1;
		} else {
			val[x] = -1;
		}
	}
}

考慮如何對答案進行計數。

首先顯然的是如果一個值已經確定為 \(U\),那麼它一定得是 \(U\),此時值與它有關的不管需不需要取反都必須是 \(U\)

其次如果經過操作後,我們得到了 \(x\) 最後的值是它本身取反,那也一定是 \(U\),因為只有 \(U\) 取反後還是本身,此時與它和它取反相等的值都要是 \(U\)

對於一個 \(x\) 等於另外一個數然而另外一個數確定為 \(u\)

不難得到別的情況都有一種方案使得不是 \(U\)

如何處理和某個值相等 / 相反的值?

這是擴充套件域並查集的經典問題。

\(x\) 取反在並查集中變成 \(x+n\)

如果一個 \(x\) 必須和 \(y\) 相等,那麼 \(x\)\(y\) 取反後也要相等,所以在並查集中合併 \(x,y\)\(x+n,y+n\)

如果一個 \(x\) 要和 \(y\) 取反後相等,那麼 \(x\) 取反後和 \(y\) 相等,所以合併 \(x,y+n\)\(x+n,y\)

注意計算答案時一個集合的答案只能算一次,但一個集合中可能有多個數都滿足,所以算完一個集合的答案後要把這個集合的 \(siz\) 清空。

讓一個數 \(x\) 的答案只在他本身取到而不在取反取到,讓 \(x+n\) 的大小一開始設為 0。

程式碼思路比較清晰,應該有比我更簡潔的寫法。

int find(int x) {
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int x, int y) {
	x = find(x), y = find(y);
	if (x == y) return;
	fa[x] = y, siz[y] += siz[x];
}

void add(int &res, int x) {
	x = find(x);
	res += siz[x];
	siz[x] = 0;
}

void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++) {
		ok[i] = false, val[i] = 0;
		p[i] = i, sig[i] = 1;
	}
	for (int i = 1; i <= n * 2; i ++) {
		fa[i] = i, siz[i] = i <= n;
	}

	while (m --) {
		char op;
		int x, y;
		cin >> op;
		if (op == '+') {
			cin >> x >> y;
			if (ok[y]) {
				ok[x] = true;
				val[x] = val[y];
			} else {
				ok[x] = false;
				p[x] = p[y];
				sig[x] = sig[y];
			}
		} else if (op == '-') {
			cin >> x >> y;
			if (ok[y]) {
				ok[x] = true;
				val[x] = -val[y];
			} else {
				ok[x] = false;
				p[x] = p[y];
				sig[x] = -sig[y];
			}
		} else {
			cin >> x;
			ok[x] = true;
			if (op == 'U') {
				val[x] = 0;
			} else if (op == 'T') {
				val[x] = 1;
			} else {
				val[x] = -1;
			}
		}
	}

	for (int i = 1; i <= n; i ++) {
		if (ok[i]) continue;
		int x = p[i];
		if (sig[x] == 1) {
			merge(i, x), merge(i + n, x + n);
		} else {
			merge(i + n, x), merge(x + n, i);
		}
	}

	int res = 0;
	for (int i = 1; i <= n; i ++) {
		if (ok[i] && val[i] == 0) {
			add(res, i), add(res, i + n);
		} else if (find(i) == find(i + n)) {
			add(res, i);
		}
	}

	cout << res << "\n";
}

相關文章