题意:
Sam和Jon在玩取石子游戏,现在一共有n堆石子,第i堆石子初始时有si个石头。每次,轮到的那个玩家可以任选一堆石子并从中任意取走一定数量的石头,先不能取的人算输
当然,游戏有特殊的约束,对于第i堆石子,如果之前在这堆石子取走过k块石头,那么再也不能从这堆石子里一次性取走k块石头。通俗地说,假如某堆石子有16块,某次取走了8块,那么下一次如果要取这堆石子,可以从1~7任选一个数字但就是不能选择8。
现在,Jon先手这局游戏,Sam想知道在两个人都操作正确的情况下他是否有必胜策略
n <= 1E6 si <= 60
solution:
其实就是用sg定理瞎做一下就行了,不过现场碰到题的时候苟蒻我并不会sg定理啊。。。(E过的人这么多活该没得涨分- -)
那既然是刚学的知识,这里就直接当是sg定理的学习笔记好了
定义F(x)为游戏进行到状态x的时候所有能够到达的后继状态的集合,sg(x)为状态x的sg函数
有sg(x) = mex{sg(y)|y ∈ F(x)},而mex{}是定义在自然数集上的函数,值为集合中第一个未出现过的自然数
显然,如果状态T为终止态(已经无法对游戏进行任何操作),则应有sg(T) = 0
先考虑一种简单的情形,当前的局面只有一堆,含有k个石头的,从未进行过任何操作的石子
那么从这个局面开始,每个状态可以用一个唯一的二元组(res,o)表示,其中,res代表剩下的石头数,o代表已经取出过的石头数的集合
注意到si <= 60,所以完全可以通过记忆化搜搜确定出sg((1,0)) ~ sg((60,0))。
先打张表存好,然后记SG(i) = sg((i,0))
先不加证明地给出sg定理的结论:记sum = SG(s1) xor SG(s2) xor SG(s3) ... xor SG(sn),若sum不为0,先手必胜,否则先手必败
考虑整场游戏的终止态为T,显然此时每堆石子的SG值都为0,因为无法继续操作了,那么此时的sum值也应该为0。
可以证明:只要当前sum值不为0,总有一种方案,使得操作之后sum值为0
记当前sum值二进制数码中最高位为k,那么在所有石子中,任选一堆SG值的二进制数码第k位为1的石子,假设它的SG值为x,记y = sum xor x
显然,有x > y,因为,如果x的最高位也为k,那么y的最高位肯定不是k了,此时y < x成立。如果x的最高位大于k,那么y的第k位为0,大于k的位置值与x相同,所以y < x也是成立的
既然y < x,那么x的所有后继状态中,必定至少有一个状态的SG值为y,这个由mex的性质就知道了,如果没有y这个值,那怎么可能SG值为x?
只要让这堆石子转移到SG值为y的那个状态,就有sum' = sum xor x xor y = 0,证明完毕
再有:只要当前sum值为0,那么无论当前玩家如何操作,下一个状态的sum值必不为0
因为sum为0的状态当且仅当所有石子目前状态中的SG值为0,而只要是SG值为0的状态能转移一定是转移到SG值不为0的状态,因此总有sum' = sum xor x(x > 0),证明完毕
假如先手拿到的状态为sum > 0,那么他总有一种方案能使对手拿到的状态sum = 0,而他的对手无论采取怎样的决策都只能让下一个状态的sum > 0。在本场游戏中,先碰到终止态T的人就宣告游戏失败了,因此,只要先手的人足够聪明,他要取胜只需时刻维护交到对手的状态有sum = 0即可,而构造方案已经证明了
至于sum = 0时先手必败,反过来就是了
那就先贴个打表代码吧。。。开个map一下就搜好了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long LL;
const int N = 66;
struct data{
int x; LL k; data(){}
data(int x,LL k): x(x),k(k){}
bool operator < (const data &B) const
{
if (x < B.x) return 1;
if (x > B.x) return 0;
return k < B.k;
}
};
int n,tot,sg[N];
map <data,int> s;
int Dfs(int x,LL k)
{
if (s.count(data(x,k))) return s[data(x,k)];
bool bo[N]; memset(bo,0,sizeof(bo));
for (LL i = 1; i <= x; i++)
{
if (k & (1LL << i)) continue;
bo[Dfs(x - i,k | (1LL << i))] = 1;
}
for (int i = 0; i < N; i++)
if (!bo[i]) return s[data(x,k)] = i;
}
int main()
{
#ifdef DMC
freopen("DMC.txt","w",stdout);
#endif
for (int i = 1; i <= 60; i++)
{
sg[i] = Dfs(i,0); s.clear();
printf("%d,",sg[i]); if (i % 10 == 0) puts("");
}
return 0;
}
然后解决问题的代码其实很好写的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long LL;
const int sg[60] = {1,1,2,2,2,3,3,3,3,4,
4,4,4,4,5,5,5,5,5,5,
6,6,6,6,6,6,6,7,7,7,
7,7,7,7,7,8,8,8,8,8,
8,8,8,8,9,9,9,9,9,9,
9,9,9,9,10,10,10,10,10,10};
int n,tot;
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
cin >> n;
while (n--)
{
int x; scanf("%d",&x);
tot ^= sg[x - 1];
}
puts(tot ? "NO" : "YES");
return 0;
}