分割游戏(博弈论)
题目
原题链接:https://atcoder.jp/contests/abc368/tasks/abc368_f
问题描述
你被给定一个由 N 个正整数 A=(A1,A2,…,AN) 组成的序列,其中每个元素至少为 2 。安娜和布鲁诺使用这些整数进行游戏。他们轮流进行操作,安娜先手,执行以下操作。
自由选择一个整数 i (1≤i≤N) 。然后,自由选择一个不是 Ai 本身的 x 的正除数 Ai ,并将 Ai 替换为 x 。
无法执行操作的一方输掉游戏,另一方获胜。假设双方都为胜利而最优地行动,确定谁会获胜。
约束
1≤N≤105
2≤Ai≤105
所有输入值都是整数。
输入
第一行输入一个整数N,第二行输入N个正整数Ai
输出
如果安娜赢得游戏,则打印 Anna ,如果布鲁诺赢得游戏,则打印 Bruno 。
示例输入1
3
2 3 4
示例输出1
Anna
例如,游戏可能按以下步骤进行。请注意,这个例子不一定代表双方的最佳玩法:
安娜将 A3 改为 2 。
布鲁诺将 A1 改为 1 。
安娜将 A2 改为 1 。
布鲁诺将 A3 改为 1 。
安娜在自己的回合无法操作,因此布鲁诺获胜。
实际上,对于这个示例,如果安娜采取最佳策略,她总是获胜。
示例输入2
4
2 3 4 6
示例输出2
Bruno
思路
这是一道博弈论中关于SG函数的题,先来分析游戏规则,给出N个正整数组成的序列,每次可以将其中的某个数Ai更改为Ai的真因数x,当剩余没有可更改的数时,也就是剩下的数都为1的时候,该局玩家输掉游戏。那么子游戏的终止状态就是变成一,每轮游戏后可能达到的后继状态即为这个数的所有真因数,那么就可以开始写SG函数了。
用一个数组sg[i]来存储i的SG值,集合S来存储i的所有因数的SG值,sg函数的计算过程如下:
for(int i=1; i<=N; i++){//计算sg的值
for(int j=0; j<=N+5; j++){//从0开始检查j是否在集合中
if(S[i].count(j)) sg[i]++;//若j在集合中,增加sg[i]的值
else break;//遇到不在集合中的数,跳出循环,此时sg[i]的值就是mex值
}
for(int j=i+i; j<=N; j+=i) S[j].insert(sg[i]);//将当前数字i的sg值添加到所有i的倍数中
}
该题完整代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5;
int n,a[N];
int main(){
ios::sync_with_stdio(false); cin.tie(nullptr);
cin>>n;
vector<int> a(n+1);
vector<set<int>> S(N+5);//存储数字i的所有可能后继状态的sg值,在这里即存储i的所有真因数
vector<int> sg(N+5,0);
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=N; i++){//计算sg的值
for(int j=0; j<=N+5; j++){//从0开始检查j是否在集合中
if(S[i].count(j)) sg[i]++;//若j在集合中,增加sg[i]的值
else break;//遇到不在集合中的数,跳出循环,此时sg[i]的值就是mex值
}
for(int j=i+i; j<=N; j+=i) S[j].insert(sg[i]);//将当前数字i的sg值添加到所有i的倍数中
}
int res=0;
for(int i=1; i<=n; i++) res^=sg[a[i]];//组合游戏的sg值等于各子游戏sg值的异或和
if(res==0) cout<<"Bruno"<<"\n";//sg值等于0,则先手必败
else cout<<"Anna"<<"\n";
return 0;
}
关于SG函数
SG函数及其在博弈论中的应用
SG函数(Sprague-Grundy函数)是博弈论中用于分析 impartial games(公平组合游戏)的重要工具。公平组合游戏是指:双方轮流操作、信息完全、无随机因素、且每个局面的可行操作只依赖于局面本身。SG函数可以帮助我们判断每个游戏状态的胜负情况,以及组合多个游戏时的胜负。
SG函数的定义
1、对于每个游戏状态,其SG值定义为所有后继状态SG值集合的mex(最小排除值)。mex是指最小的不在该集合中的非负整数,例如集合S={0,1,3},它的mex值就等于2。
2、如果将某个点A的后继状态记为集合 S,则 SG(A)=mex{SG(s)∣s∈S} 。
例如这道题,通过一轮游戏操作后,可以将6变为6的真因数1,2,3,那么SG(6)=mex{SG(1),SG(2),SG(3)}。
3、如果某个状态的SG值为0,则当前玩家必输;否则,当前玩家必胜。
简单说一下这点。我们知道,在博弈论中,有必胜态N和必败态P,它们的定义分别是:若某个状态通过一轮游戏操作后,只能转换成必胜态的,则它就是必败态;若某个状态,有至少一种方法,可以下一轮的状态变为必败态,则它是必胜态。
这里给出一张状态转移图,当然不是本题的,只是用来看一下。

这是一个尼姆博弈的图,大概就是说有两堆石子,一堆10个一堆5个,每次可以取2个或者5个,若是无法取石子(剩余数为1)或无石子可取(剩余0),则输掉游戏。那么sg[0]和sg[1]都标记为0,也就是图中每个数右上角的小数字。
先根据初始石子数和取石子的方法,画出状态转移图,再从终止态倒推,按照sg的定义得到每个状态的sg值。注意到,当后续状态集合中不存在0时,该状态的sg值等于0,即当sg值等于0时,不论如何,它的下一个状态都只能变成sg值不为0的状态,符合必败态的定义。而当后续状态中存在0时,则表示至少有一种方法,使它的下一个状态可以变为必败态,则该状态为必胜态。
组合游戏中的SG函数
1、当游戏由多个独立子游戏组成时,整个游戏的SG值是各子游戏SG值的异或和。
即,如果子游戏的SG值分别为 sg1,sg2,…,sgnsg1,sg2,…,sgn,则整体SG值为 sg1⊕sg2⊕⋯⊕sgnsg1⊕sg2⊕⋯⊕sgn。
2、如果异或和为0,先手必输;否则先手必胜。
这点也可以简单说一下。首先要明确,所有子游戏的最终状态的sg值肯定是都为0,此时所有子游戏sg值的异或和(记为SG)也为0。
假设现在有三个子游戏,它们的初始状态的sg值分别为2,5,7,那么SG的计算过程如下:

SG=0,无论如何,只要改变了其中任意一个0或1,SG都会变成一个非0的数,即只能变成必胜态。
而若是sg值为3,5,7,SG=1,只要我将3变成2,或者将5变成4,或者将7变成6,都可以使SG变成0,即存在至少一种方法变成必败态。

在本问题中的应用
本问题中,每个整数 Ai 可以看作一个独立的子游戏。操作是选择一个真除数(即不等于 Ai 本身的除数),将 Ai 替换为该除数。无法操作时输。
对于数字 i,其后续状态是所有真除数 d(即 d∣i且 d<i)。
因此,SG(i)=mex{SG(d)∣d 是 i 的真除数}。
代码实现思路
预处理SG值:从数字1开始到100000,计算每个数字的SG值。
数字1没有真除数,因此 SG(1)=0SG(1)=0。
使用集合存储后继SG值:对于每个数字 jj,用集合 S[j]S[j] 存储其所有真除数对应的SG值。
通过遍历每个数字 ii,将其SG值添加到所有倍数 jj(即 j=2i,3i,…≤Nj=2i,3i,…≤N) 的集合 S[j]S[j] 中。这是因为 ii 是 jj 的真除数。
计算mex:对于每个数字 ii,从0开始检查整数是否在 S[i]S[i] 中,直到找到第一个不在集合中的整数,即为 SG(i)SG(i)。
判断胜负:对于输入序列 AA,计算所有 SG(Ai)SG(Ai) 的异或和。如果异或和为0,布鲁诺赢;否则安娜赢。
——————————————————
该文在CSDN上已发过,此处为二次编辑
https://blog.csdn.net/SDSSpJ306/article/details/150784036?spm=1011.2124.3001.6209

浙公网安备 33010602011771号