此题算是 s g sg sg 函数的基本应用,前置知识(本人之前的文章):博弈论 (SG函数)
题面
有
n
n
n 堆石子,每次可以将一堆石子的石子数量变成它的约数(不能是它自己),轮流操作,直到一方无法操作。
转化
当一堆石子数量是 x x x,它的可以转移到的状态是 y ( x % y = 0 , x ≠ y ) y(x\%y=0,x\ne y) y(x%y=0,x=y),遂连一条从 x x x 到 y y y 的有向边。对于每堆石子都建立起一个 DAG,维护 s g sg sg 函数,初始值 s g 1 = 0 sg_1=0 sg1=0(这里并不需要真的建边,按照拓扑序排序,因为 y < x y<x y<x,只要 i i i 按从小到大的顺序枚举,每一次花 O ( i ) O(\sqrt{i}) O(i) 的时间遍历 i i i 的所有约数就好)。把这些石子的初始状况的 s g sg sg 函数值异或起来,如果是 0 0 0,Bruno 赢;非 0 0 0,Anna 赢。
代码
#include<bits/stdc++.h>
using namespace std;
int n,a,sg[100005],ans,maxx;
bool flag[100005];
//flag记录这个数在a数组里是否出现奇数次,偶数次的话把偶数个相等的sg异或起来是0,对答案无影响
vector<int> res;//记录邻居中有哪些sg值,方便算MEX
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a);
flag[a]=!flag[a];
maxx=max(maxx,a);
}
for(int i=2;i<=maxx;i++){
res.clear();
for(int j=1;j*j<=i;j++){
if(i%j) continue;
res.push_back(sg[j]);
if(j!=1&&j*j!=i) res.push_back(sg[i/j]);
}
sort(res.begin(),res.end());
int si=res.size();
for(int j=0,pos=0;j<=maxx,pos<si;j++){
while(pos<si&&res[pos]<j) pos++;
if(pos>=si||pos<si&&res[pos]!=j){
sg[i]=j;
break;
}
}
// cout<<i<<' '<<sg[i]<<endl;
if(flag[i]) ans=ans^sg[i];
}
if(ans) printf("Anna\n");
else printf("Bruno\n");
return 0;
}