【BZOJ2410】Nim游戏(博弈论)

点此看题面

  • 有\(n\)个格子,初始有一部分被染上了颜色。
  • 两人轮流从\(k\)种颜色中选择一种给某个未被染色的格子染上颜色,要求不能有相邻格子被染上了相同颜色。
  • 要求判断先手是否有必胜策略。
  • \(n\le10^5\)

\(k\ge3\)的情况

如果\(k\ge 3\),每个格子必然都能被染上颜色。

因此胜负情况只取决于未被染色的格子数的奇偶性,即当且仅当未被染色的格子数为奇数时先手胜。

\(k=1\)的情况

设\(SG1(n)\)表示\(k=1\)时在\(n\)个空格子中博弈的\(SG\)函数。

一种情况是选择了边上某个格子,后继状态为\(SG1(n-2)\)。

另一种情况是选择了中间某个格子,那么它以及相邻两侧共三个格子都不能再选择,后继状态为\(SG1(i)\oplus SG1(n-3-i)\)。

因此列出转移方程:

\[SG1(n)=\texttt{mex}\{SG1(n-2),\texttt{mex}_{i=0}^{n-3}\{SG1(i)\oplus SG1(n-3-i)\}\} \]

这是一个\(O(n^2)\)的转移方程,但似乎并不能再优化了。

于是考虑对打出的表找规律,发现从第\(52\)项开始它就存在一个大小为\(34\)的周期,那么只要求出\(SG\)函数前\(85\)项即可。

\(k=2\)的情况

对于一个大小为\(S\)的区间,考虑其在\(k=2\)时的\(SG\)函数。

两端有限制的情况

发现只有两种可能的终止态:区间两端限制颜色不同,区间大小为\(0\)或\(1\)。

此外,发现当区间两端限制颜色不同时一次操作只会产生两个限制颜色都不同或都相同的区间,区间两端限制颜色相同时一次操作会产生一个限制颜色不同的区间和一个限制颜色相同的区间。

于是可以猜想此时的\(SG\)函数只与区间两端限制颜色是否相同有关,进而发现当限制颜色不同时\(SG\)函数值为\(0\),当限制颜色相同时\(SG\)函数值为\(1\)。

一端有限制的情况

归纳可证大小为\(S\)的区间\(SG\)值就是\(S\)。

如果我们选择在第\(i\)个位置(\(1\le i\le S\))填上与限制颜色不同的颜色,那么后继状态是\((i-1)\oplus 0\),即后继状态包含\(0\sim S-1\)。

如果我们选择在第\(i\)个位置(\(1\le i<S\))填上与限制颜色相同的颜色,那么后继状态是\((i-1)\oplus 1\),满足\((i-1)\oplus1\le i<S\)。

因此后继状态的\(\texttt{mex}\)就是\(S\),即\(SG\)值等于\(S\)。

没有限制的情况

选择在第\(i\)个位置上填入一个数的后继状态是\((i-1)\oplus(n-i)\)。

显然,只有当\(n\)是奇数的时候才有可能出现\((i-1)\oplus(n-i)=0\)。

又由于没有限制肯定是全局的情况,不需要求出具体的\(SG\)值,只要判断先手是否必胜即可。

代码:\(O(Tn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,k,a[N+5],vis[N+5],sg[N+5];
I int SG1(RI x) {return sg[x<=52?x:52+(x-52)%34];}//求k=1时在n个空格子中博弈的SG函数
I int SG2(CI l,CI r) {return 1<=l&&r<=n?a[l]==a[r]:r-l-1;}//求k=2时在区间(l,r)中博弈的SG函数,根据几端有限制分类
I bool Solve()
{
	RI i,j,t=0;if(k>=3) {for(i=1;i<=n;++i) !a[i]&&(t^=1);return t;}//k≥3的情况
	if(k==1) {for(j=0,i=1;i<=n;++i) a[i]&&(t^=SG1(max(j?i-j-3:i-2,0)),j=i);return t^=SG1(max(j?n-j-1:n,0)),t;}//k=1的情况
	for(i=1;i<=n&&!a[i];++i);if(i>n) return n&1;//k=2的情况(没有限制)
	for(j=0,i=1;i<=n;++i) a[i]&&(t^=SG2(j,i),j=i);return t^=SG2(j,n+1);//k=2的情况(有限制)
}
int main()
{
	RI i,j;for(sg[1]=1,i=2;i<=85;++i) {for(vis[sg[i-2]]=i,j=0;j<=i-3;++j) vis[sg[j]^sg[i-3-j]]=i;W(vis[sg[i]]==i) ++sg[i];}//预处理SG1的前85项
	RI Tt;scanf("%d",&Tt);W(Tt--) {for(scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",a+i);puts(Solve()?"y":"n");}return 0;
}
上一篇:题解——P3185 [HNOI2007]分裂游戏


下一篇:博弈论学习笔记