题意:
给你n个石堆,每一个石堆ci个石头,每一次取选取一个石堆(数量k>0)拿去m个(k%m==0)
两个人轮流取,一旦一个人不能再取,则视为该人输了。
问你先手第一次取,有多少种取法保证必胜
解析:
这个其实就是一个简单的Nim博弈的应用。
之前一篇博客上也有他大神讲Nim的链接
我就简单总结一下.
对于Nim博弈(取石头),有一个很重要的结论就是
n堆石头,每堆石头ci个,先手必败的充要条件就是
然后介绍sg函数
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。
对于必败态的充要条件就是
然后就是一个重要结论
这个结论不知道可以看大神的博客,直接记住也可以。
根据上面的结论,必败态的充要条件就是,
那么对于Nim游戏先手必败的结论也可以写成()
然后我们就可以把一个Nim游戏分解n个独立的子游戏,每一个独立的子游戏里面再分别求各自的sg函数
例如
有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……我们可以把它看作3个子游戏,第1个子游戏只有一堆石子,每次可以取1、2、3颗,很容易看出x%4==0时处于P局面,即x颗石子的局面的SG值是x%4,(即把.中的值改成原值%4)。第2个子游戏也是只有一堆石子,每次可以取奇数颗,经过简单的画图可以知道这个游戏有x颗石子时的SG值是x%2。第3个游戏有n-2堆石子,就是一个Nim游戏。对于原游戏的每个局面,把三个子游戏的SG值异或一下就得到了整个游戏的SG值,然后就可以根据这个SG值判断是否有必胜策略以及做出决策了。其实看作3个子游戏还是保守了些,干脆看作n个子游戏,其中第1、2个子游戏如上所述,第3个及以后的子游戏都是“1堆石子,每次取几颗都可以”,称为“任取石子游戏”,这个超简单的游戏有x颗石子的SG值显然就是x。其实,n堆石子的Nim游戏本身不就是n个“任取石子游戏”的和吗?
最后再把他们异或起来当前局面必败的话当前仅当
然后对于求sg函数,方法有很多了:
解题模型:
1.把原游戏分解成多个独立的子游戏,则原游戏的SG函数值是它的所有子游戏的SG函数值的异或。
即sg(G)=sg(G1)^sg(G2)^...^sg(Gn)。
2.分别考虑没一个子游戏,计算其SG值。
SG值的计算方法:(重点)
1.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
2.可选步数为任意步,SG(x) = x;
3.可选步数为一系列不连续的数,用模板计算。
模板1:打表
模板二:DFS
一般DFS只在打表解决不了的情况下用,首选打表预处理。
3.计算sg(G)=sg(G1)^sg(G2)^...^sg(Gn),
sg(G)=0,即P-Position,即先手比败。
最后就是这道题目,
在n=1的时候,找不到sg的规律
我就把n=2的情况打了个表,发现sg(x)=k+1(k是x质因数分解后,2的指数,例如sg(8)=4,sg(7)=1,sg(2)=2,sg(24)=4)
然后就可以把1e5的sg函数打表,枚举当前局面走一步所能达到的局面,根据sg的异或和=0,来判断当前局面是不是必败态
是的话ans++
这道题好像可以直接按照模板,sg打表,题解好像是这样的。
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 1e5+10;
int sg[MAXN];
int a[MAXN];
vector<int> b[MAXN];
void cal(int x,int id)
{
for(int i=1;i*i<=x;i++)
{
if(x%i==0)
{
b[id].push_back(i);
if(x/i!=i) b[id].push_back(x/i);
}
}
}
int main()
{
int n;
scanf("%d",&n);
sg[0]=0;
for(int i=1;i<MAXN;i++)
{
int tmp=1;
int k=i;
while(k&&k%2==0) tmp++,k=k/2;
sg[i]=tmp;
}
int sum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
cal(a[i],i);
sum^=sg[a[i]];
}
int ans=0;
for(int i=1;i<=n;i++)
{
int in=sum^sg[a[i]];
for(int j=0;j<b[i].size();j++)
{
int out=in^sg[a[i]-b[i][j]];
if(out==0) ans++;
}
}
printf("%d\n",ans);
return 0;
}