Wannafly挑战赛23 B 游戏(sg函数+Nim博弈)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37025443/article/details/82290137

题目链接

题意:

给你n个石堆,每一个石堆ci个石头,每一次取选取一个石堆(数量k>0)拿去m个(k%m==0)

两个人轮流取,一旦一个人不能再取,则视为该人输了。

问你先手第一次取,有多少种取法保证必胜

解析:

这个其实就是一个简单的Nim博弈的应用。

之前一篇博客上也有他大神讲Nim的链接

我就简单总结一下.

对于Nim博弈(取石头),有一个很重要的结论就是

n堆石头,每堆石头ci个,先手必败的充要条件就是c1\bigoplus c2 \bigoplus c3\bigoplus....\bigoplus cn=0

然后介绍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的后继 }。

对于必败态的充要条件就是sg(x)=0

然后就是一个重要结论sg [c1] \bigoplus sg [c2] \bigoplus sg [c3] \bigoplus....\bigoplus sg [cn] =sg[C]

这个结论不知道可以看大神的博客,直接记住也可以。

根据上面的结论,必败态的充要条件就是sg(x)=0

那么对于Nim游戏先手必败的结论也可以写成sg[C]=0sg [c1] \bigoplus sg [c2] \bigoplus sg [c3] \bigoplus....\bigoplus sg [cn] =sg[C]

然后我们就可以把一个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个“任取石子游戏”的和吗?

最后再把他们异或起来当前局面必败的话当前仅当0=sg1 [c1] \bigoplus sg2 [c2] \bigoplus sg3 [c3] \bigoplus....\bigoplus sgn [cn]

然后对于求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;
}

猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/82290137