1. 程式人生 > 其它 >【NOI2022省選挑戰賽 Contest3 C】取石子(博弈論)(結論)

【NOI2022省選挑戰賽 Contest3 C】取石子(博弈論)(結論)

給你一個序列,兩個人輪流操作每次可以拿走序列兩頭的其中一個數。 然後誰選的數的異或和大誰就贏。 然後每次問你先手必勝還是後手必勝還是平局。

取石子

題目連結:NOI2022省選挑戰賽 Contest3 C

題目大意

給你一個序列,兩個人輪流操作每次可以拿走序列兩頭的其中一個數。
然後誰選的數的異或和大誰就贏。
然後每次問你先手必勝還是後手必勝還是平局。

思路

首先異或嘛,肯定是按位數從高到低看。
那不難想出如果對於一位它在偶數個數中出現,那要麼大家都是 \(0\),要麼都是 \(1\)

所以我們只需要看最高位的第一個所有數異或起來不是 \(0\) 的位置,那如果全是 \(0\) 就平手。
然後問題就變成了一個 \(01\) 串(奇數個 \(1\)),每次可以取頭尾,取到奇數個的獲勝。

然後發現如果長度是偶數就先手必勝,因為你奇數位置和偶數位置 \(1\)

的個數肯定是一奇一偶的,而且先手顯然可以取最優的奇數位置或偶數位置,所以必勝。
接著看偶數長度,那先手第一步必須取 \(1\),不然狀態就變成了後手變先手,然後長度偶數,就是後手勝了。
那也因為這個條件,接下來每次先手必須取後手一樣的數。

所以我們可以先列舉先手取的是頭還是尾,看是否可能獲勝。
然後可以這樣看,先不斷刪去頭尾相同的,然後接著序列就必須是 \(00110011....\) 這樣兩兩弄下去(也就是每兩個位置必須一樣,不一定要交錯,可以 \(11000011\)

然後你還發現這樣除了先手多了一個 \(1\),它們取的數量是 \(\frac{cnt-1}{2}\)\(cnt\) 是整個序列中 \(1\)

的個數),因此我們這時候還需要保證這個值是一個偶數(這樣先手加上一開始拿的才會有奇數個)。

然後就好了。

程式碼

#include<cstdio>

using namespace std;

int t, n, a[10001];

bool check(int l, int r) {
	while (l <= r && a[l] == a[r]) l++, r--;
	for (int i = l; i <= r; i += 2)
		if (a[i] != a[i + 1]) return 0;
	return 1;
}

int main() {
	scanf("%d", &t);
	while (t--) {
		scanf("%d", &n);
		int va = 0; for (int i = 1; i <= n; i++) scanf("%d", &a[i]), va ^= a[i];
		if (!va) {
			printf("Draw\n"); continue;
		}
		for (int i = 30; i >= 0; i--) {
			if (!((va >> i) & 1)) continue;
			int cnt = 0;
			for (int j = 1; j <= n; j++) {
				a[j] = (a[j] >> i) & 1;
				if (a[j]) cnt++;
			}
			if (n & 1) {
				if (((cnt - 1) >> 1) & 1) printf("Bob\n");
					else if ((a[1] && check(2, n)) || (a[n] && check(1, n - 1))) printf("Alice\n");
						else printf("Bob\n");
			}
			else printf("Alice\n");
			break;
		}
	}
	
	return 0;
}