[博弈dp] UVA1500 Alice and Bob

posted on 2024-04-29 06:18:45 | under | source

更新了下题解。

dp 做法

首先,令 \(S=sum+n-1\)。这是最大操作数。

然后有个 simple 的想法:每次操作会改变 \(S\) 奇偶,如果 \(S\) 是奇数,先手必胜;反之后手必胜。

但是很容易被 hack,原因在于没有考虑 \(a_i=1\) 的情况。在此情况下可以移走 \(a_i=1\) 的堆将其清零,然后 \(S\) 奇偶不变。

不过,对于 \(\forall a_i>1\) 的情况,结论依旧成立。胜者只要每次将场上 \(a=1\) 的堆合并,那么败者怎么操作都无法将某一堆删成 \(0\)。即最优策略下每次操作都会改变奇偶。

这启示我们按照将所有石子分为 \(a_i=1\)\(a_i>1\) 的两部分。定义 \(f_{cnt,S}\) 表示 \(cnt\)\(a_i=1\)\(a_i>1\) 部分的 \(sum+n-1\)(即最大操作数)为 \(S\) 时,先手是否必胜。

然后考虑 \(f_{cnt,S}\) 转移到哪些局面:

  1. 只对 \(a_i>1\) 的进行操作,删除或合并都一样:\(f_{cnt,S-1}\)。这里无需考虑 \(2\) 堆挪去一个石子变成 \(1\) 堆,因为根据先前讨论赢家可以合并从而让这个 \(1\) 堆无法被清空。

  2. 删除 \(a_i=1\) 的:\(f_{cnt-1,S}\)

  3. 合并两个 \(a_i=1\) 的,注意特判 \(S=0\)\(f_{cnt-2,S+2+[S>0]}\)

  4. 合并 \(a_i=1\)\(a_i>1\)\(f_{cnt-1,S+1}\)

然后注意下边界:

  1. \(cnt=0\):必胜条件为 \(S\) 是奇数。
  2. \(cnt>0\)\(S=1\),此时把 \(S\) 中这个 \(1\) 堆挪去会导致两次变化,所以需要返回 \(f_{cnt+1,0}\)

将上述过程用记忆化搜索实现即可。复杂度 \(O(n\sum a)\)

代码

#include<bits/stdc++.h>
using namespace std;

#define MIN(a, b) a = min(a, b)
const int N = 55, M = 50005;
int T, n, a[N], f[N][M];

inline int dfs(int cnt, int S){
	if(f[cnt][S] != -1) return f[cnt][S];
	if(!cnt) return f[cnt][S] = (S & 1);
	if(S == 1) return f[cnt][S] = dfs(cnt + 1, 0);
	int res = 2;
	if(cnt) MIN(res, dfs(cnt - 1, S));
	if(cnt >= 2) MIN(res, dfs(cnt - 2, S + 2 + (S ? 1 : 0)));
	if(S) MIN(res, dfs(cnt, S - 1));
	if(cnt && S) MIN(res, dfs(cnt - 1, S + 1));
	return f[cnt][S] = (res == 0 ? 1 : 0);
}
signed main(){
	memset(f, -1, sizeof f);
	cin >> T;
	for(int t = 1; t <= T; ++t){
		scanf("%d", &n);
		int _cnt = 0, _S = 0;
		for(int i = 1; i <= n; ++i){
			scanf("%d", &a[i]);
			if(a[i] == 1) ++_cnt;
			else _S += a[i] + 1;
		}
		_S -= (_S > 0);
		printf("Case #%d: ", t);
		if(dfs(_cnt, _S)) puts("Alice");
		else puts("Bob");
	}
	return 0;
}

大力推理做法

我们记 \(sum\) 表示只对 \(\ge 2\) 的石子堆操作的最大操作数,\(cnt\) 表示 \(=1\) 的石子堆数量。

  • 结论 \(1\):若 \(sum>2,2\mid sum,2\mid cnt\),则后手必胜。

证明:考虑归纳,首先 \(cnt=0\) 时结论成立。其它情况下先手无论如何操作,后手都能将其归约到上述条件。就算先手把一个 \(2\) 堆移成 \(1\) 堆,由于 \(sum>2\) 所以必然存在另一个 \(\ge 2\) 的堆,合并过去即可,游戏局数有限所以后手必胜。

  • 结论 \(2\):若 \(sum>2\),后手必胜当且仅当 \(2\mid sum,2\mid cnt\)

证明:

  1. \(2\nmid sum,2\mid cnt\):若 \(cnt=0\) 显然,否则先手将两个 \(1\) 堆合并,由结论 \(1\) 可知先手必胜。

  2. \(2\nmid sum,2\nmid cnt\):只需将一个 \(1\) 堆合并到 \(\ge 2\) 堆上即可。

  3. \(2\mid sum,2\nmid cnt\):将一个 \(1\) 删掉即可。

  • 结论 \(3\):若 \(sum=0,2\),后手必胜当且仅当 \(3\mid cnt\)

证明:归纳,记 \(cnt=3k+p\)

  1. \(k=0\): 手玩下即可发现结论成立。
  2. \(k>1\):当 \(p=0\) 时枚举所有情况可知后手必胜;当 \(p=1\) 时先手将 \(1\) 堆删去即可;\(p=2\) 时先手合并 \(1\) 堆即可。

复杂度 \(O(n)\)

代码

#include<bits/stdc++.h>
using namespace std;

int T, n, a, sum, c1, c2;

signed main(){
	cin >> T;
	while(T--){
		scanf("%d", &n), sum = c1 = c2 = 0;
		for(int i = 1; i <= n; ++i){
			scanf("%d", &a);
			if(a > 1) sum += a, ++c2;
			else ++c1;
		}
		sum += max(0, c2 - 1);
		if(sum > 2)
			if(sum % 2 == 0 && c1 % 2 == 0) puts("NO");
			else puts("YES");
		else 
			if(c1 % 3) puts("YES");
			else puts("NO");
	}
	return 0;
}
posted @ 2026-01-12 20:07  Zwi  阅读(0)  评论(0)    收藏  举报