AcWing 893. 《算法基础课》博弈论--集合-Nim游戏
原题链接
简单
作者:
藕丝泥霸
,
2024-02-12 21:40:42
,
所有人可见
,
阅读 33
思路
- 求出每堆石子的sg函数,再根据Nim游戏解题思路,求解所有石子堆的异或和,若为0,则是必败态,否则是必胜态
- 每个石子堆取石子状态构成一个有向图,终点表示石子无法再依据集合选取,sg值为0
- 多个石子堆对应多个有向图,就有多个sg函数值,转换为标准Nim游戏求解
- 由于对所有石子堆,取的数目都是从一个集合中取得,因此可以采用记忆化搜索策略,这样不需要对每堆石头的每个状态都计算sg函数
- 记忆化搜索体现在代码中
- 只需在最开始初始化f数组为-1,而不需要每次输入石头堆数目初始化
- 求解sg值函数,若已经计算过sg(x),直接返回
- sg函数
- 定义:在有向图当前节点的下一节点组成的集合中,不存在的最小的自然数。有向图终点sg为0
- 求解:依据定义,当前节点可达节点组成的集合中找到不存在的最小的自然数,如当前节点下一节点组成的集合sg值为{0,1,3,4},当前节点sg值为2
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int N=110,M=1e4+10;
int n,k;
int s[N],f[M];//s记录集合,f记录sg函数的取值
//求sg函数
int sg(int x){
//记忆化搜索
if(f[x]!=-1) return f[x];
set<int> g;
for(int i=0;i<k;i++){
if(x>=s[i]) g.insert(sg(x-s[i]));
//递归到有向图终点,此时s[i]<x,无法再依据集合取石子,此时sg[x]=0
}
//根据sg函数定义求x的sg值,即 不在集合g中最小的自然数
for(int i=0;;i++){
if(!g.count(i)) return f[x]=i;
}
}
int main(){
scanf("%d",&k);
for(int i=0;i<k;i++) scanf("%d",&s[i]);
scanf("%d",&n);
//由于任何石子堆的操作都是从一个集合中取数,并且不同的石头堆数目为x时,sg(x)相同
//因此只需要初始化一次即可,这也是记忆化搜索的依据
memset(f,-1,sizeof f);
int res=0;
while(n--){
int x;
scanf("%d",&x);
res=res^sg(x);
}
if(res) puts("Yes");
else puts("No");
return 0;
}