CF2032F - Peanuts

CF2032F - Peanuts

分析

本题如果只有一个盒子,因为最后一个操作的人是赢家,所以就是 Nim 游戏,先手必胜的充要条件是所有 \(a_i\) 异或和不为 \(0\)

再考虑有两个盒子,显然最后一段必须是 Nim 游戏,我们试着从最后一段的状态反推出前一段的情况。

  • 如果后一段满足先手必败,Alice 想要赢就必须让 Jack 成为后一段的先手。也就是说前一段需满足先手必胜
  • 如果后一段满足先手必胜,Alice 想要赢就必须让 Jack 拿走前一段的最后一堆,这样她就可以趁机成为第二段的先手。即前一段需满足先手必败

说明一下,上述所说的所有先手必胜或必败都是针对一个盒子内的先手。之所以这么想,就是把盒与盒之间独立出来,这样才可能挖掘出 dp 需要的最优子结构

说回来,上述第二种情况中,Alice 和 Jack 都想让自己在第一局输掉,这是 反常游戏(取走最后一颗棋子的人是败者),先手必胜的条件有所不同:

  • \(a_i\) 全为 \(1\),先手必胜当且仅当个数为偶数。

  • 否则,需满足 \(a_i\) 异或和不为 \(0\)

否则就是先手必败。

掌握了两个盒子的情况怎么合并,我们就可以开始设计 dp 状态了。

状态设计

\(f_{i, 0 / 1}\) 表示前 \(i\) 个数,最后一段是 Nim​ 还是反常游戏,整局游戏先手必胜的方案数。

注意:不同游戏结尾,其必胜含义不同。

答案:\(f_{n, 0}\),理由同上,最后一段必须是 Nim 游戏。

\(f_{i, 0}\) 的转移:最后一段先手必胜,从 \(f_{i, 1}\) 转移;最后一段必败,从 \(f_{i, 0}\) 转移。

\(f_{i, 1}\) 的转移:最后一段先手必胜,从 \(f_{i, 0}\) 转移;最后一段必败,从 \(f_{i, 1}\) 转移。

直接做是 \(O(n^2)\) 的,考虑如何优化。

总的来说,胜负有两个因素决定:异或和是否为 \(0\)、全 \(1\) 情况下的奇偶性。

  • 对于异或和,判断一段异或和是否为 \(0\),可以处理异或前缀和 \(s_i\),则转化成 \(s_i\) 是否和 \(s_j\) 相等。因此可以用桶存下每种 \(s_i\) 能转移的贡献,类似的桶优化 dp 技巧也出现在了CSP-S 2024 染色,具体实现可参考我的这篇博客
  • 对于全 \(1\) 的奇偶性,简单统计就可以了。遇到全 \(1\) 的时候,第一种情况就暂时不更新,等全 \(1\) 中断后再把这一段更新进桶里面,有点类似双指针

值域过大,用 map 可以做到 \(O(nlogn)\),用哈希表或许可以 \(O(n)\) 解决。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define int ll
using namespace std;
using ll = long long;
const int N = 1e6 + 105;
const int mod = 998244353;
int T, n, sum[2];
int a[N], f[N][2], sm[2][2], s[N];
map<int, pair<int, int> >mp;
signed main(){
	cin >> T;
	F(o, 1, T){
		mp.clear();
		cin >> n;
		F(i, 1, n) {
			cin >> a[i];
			s[i] = s[i - 1] ^ a[i];
			if(!mp.count(s[i])) mp[s[i]] = {0, 0};
		}
		f[0][1] = 1;
		mp[0] = {0, 0};
		sm[0][0] = sm[1][0] = sm[1][1] = 0;
		sm[0][1] = 1;
		sum[0] = sum[1] = 0;
		int pos = 0;
		F(i, 1, n){
			if(a[i] == 1){
				(f[i][0] += (sum[1] - mp[s[i]].second) + mp[s[i]].first + mod) %= mod;
				(f[i][1] += (sum[1] - mp[s[i]].second) + mp[s[i]].first + mod) %= mod;	
				if(i & 1){
					(f[i][0] += sm[0][1] + sm[1][0]) %= mod;
					(f[i][1] += sm[1][1] + sm[0][0]) %= mod;
				}
				else{
					(f[i][0] += sm[1][1] + sm[0][0]) %= mod;
					(f[i][1] += sm[0][1] + sm[1][0]) %= mod;
				}
				pos = min(pos, i);
				(sm[i & 1][0] += f[i][0]) %= mod;
				(sm[i & 1][1] += f[i][1]) %= mod;
			}
			else{
				F(j, pos, i - 1) {
					(mp[s[j]].first += f[j][0]) %= mod;
					(mp[s[j]].second += f[j][1]) %= mod;
					(sum[0] += f[j][0]) %= mod;
					(sum[1] += f[j][1]) %= mod;
				}
				(f[i][0] += (sum[1] - mp[s[i]].second) + mp[s[i]].first + mod) %= mod;
				(f[i][1] += (sum[1] - mp[s[i]].second) + mp[s[i]].first + mod) %= mod;
				(mp[s[i]].first += f[i][0]) %= mod;
				(mp[s[i]].second += f[i][1]) %= mod;
				(sum[0] += f[i][0]) %= mod;
				(sum[1] += f[i][1]) %= mod;
				sm[0][0] = sm[0][1] = sm[1][0] = sm[1][1] = 0;
				pos = N;
			}
		}
		cout << f[n][0] << ' ' << f[n][1] << '\n'; // Alice 要拿最后一个赢,因此是正常的Nim游戏
		F(i, 1, n) f[i][0] = f[i][1] = 0;
	}
	return fflush(0), 0;
}
posted @ 2024-11-21 15:21  superl61  阅读(5)  评论(0编辑  收藏  举报