sg函数与博弈论

这个标题是不是看起来很厉害呢...

我们首先来看一个最简单的游戏。比如我现在有一堆石子,有p个,每次可以取走若干个(不能不取),不能取的人就输了。

现在假设有两个人要玩这个游戏,一个人先手,一个人后手,假设两个人都是足够聪明的AI,那么谁会赢?

显然p≠0时先手赢,他只要全部取完就行了...

我们先不管这个游戏有多傻逼,我们看一看这个游戏所隐含的模型。

比如我们把当前游戏局面抽象成一个点,把这个点往每下一步可以到达的新状态连一个边,这样就形成了一个有向无环图。(如果有环这个游戏就不会结束了)

现在我们来定义sg函数。sg(T),T表示一个状态,当T=0先手必败,其他情况后手必败。

你说这玩意儿是啥?定义怎么这么不清楚

没关系我们先来看一个新函数bool_sg(T),当T=0时先手必败,T=1时后手必败。

那么我们发现bool_sg(结束态)=0,其他情况下只有当一个状态的后继状态后手都必败时才为先手必败(显然吧)。

sg函数与它类似,不过相对更为复杂。sg(结束态)=0。

其他情况下sg(T)=mex{sg(G)} (G为T的后继状态,mex表示不在集合中的最小非负整数)

可以发现bool_sg(T)=(bool)sg(T) (也很显然吧)。

我们上面说的这个游戏中,假设我们用p这个数来表示p个石子的状态。那么可以发现sg(p)=p (p>=0)。

所以我们也可以发现先手在p>0时必胜(mdzz)。

虽然上面这个游戏的解只要目测就可以得出,但在比较复杂一点的游戏中它就能派上用场了。

比如每次要取一个斐波那契数?我们只要求sg的时候开个数组模拟mex的过程就可以得到一个平方做法啦!

例1 nim游戏

现在有n堆石子,每一次你可以从任意一堆中任意个石子,但不能不选。如果你不能选你就输了。

你是先手,现在你想知道你有没有必胜策略。

如果是一堆石子,这个sg函数就是之前说过的sg(p)=p。

现在有多堆石子了,该怎么办呢?

我们定义一些游戏的和为你可以轮流从几个游戏中任意选择一个进行操作,那么有一个性质:

一些游戏的和中状态的sg函数等于这些子游戏状态sg函数的异或值。

至于为什么是这样?其实我也不知道2333 这当然是可以证明的,想要证明可以百度啊

例2 hdu1848

一共三堆石子,分别m、n、p个。两人轮流取石子,每步可以从任意一堆中取出一个斐波那契数列中的元素,但不能不取,不能取的人就输了。假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。

多组数据,1<=m,n,p<=1000

如果只有一堆石子,可以用一个bool数组暴力求出sg函数。那么有三堆石子只要异或在一起就行了。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#define SZ 666666
int fib[SZ+5],sg[SZ+5];
bool ok[SZ+5];
#define P 2333
int main()
{
    fib[0]=fib[1]=1;
    int S=20;
    for(int i=2;i<=S;i++) fib[i]=fib[i-1]+fib[i-2];
    sg[0]=0;
    for(int i=1;i<=2000;i++)
    {
        for(int j=0;j<P;j++) ok[j]=0;
        for(int j=0;j<=S;j++)
        {
            if(fib[j]<=i) ok[sg[i-fib[j]]]=1;
        }
        for(int j=0;;j++)
        {
            if(!ok[j]) {sg[i]=j; break;}
        }
    }
    int n,m,p;
    while(scanf("%d%d%d",&n,&m,&p),n||m||p)
    {
        int sgg=sg[n]^sg[m]^sg[p];
        if(sgg) puts("Fibo"); else puts("Nacci");
    }
}

例3 hdu1850

现在还是有n堆石子,第i堆有i个石子,不能不选。如果你不能选你就输了。

你是先手,现在你只想知道你为了必胜,第一步有几种走法。

首先这还是一个nim游戏的模型,不过改成了求方案数。

如果不能必胜可以简单地输出0,否则我们求出整个游戏起始态的sg函数,假设叫s好了。

那我们可以知道s=1^2^3^4^....^n对吧。

那我们考虑第一次假设选第i堆,我们发现我们目标是这次选完让先手必败(因为这里的先手就是后手)即选完让sg函数为0。

那我们可以发现不考虑这一堆剩下的sg函数就是(s^i),那为了让选完sg函数为0,那么这堆剩下的就要是(s^i)。

于是我们发现只要判一下(s^i)<=i是否成立就行了,如果成立我们就可以一开始在i堆中选(i-(s^i))个。这也是一般博弈问题中求第一步走法的一般思路。

sg函数的基本东西讲完了,接下来是一些(相对)比较复杂(鬼畜)的应用。

例4 hdu1517

有一个数p,开始p=1,两人轮流操作,每一次可以使p乘上2~9的一个整数,最先使得p>=n的人赢。

给出n,求最优策略下先手是必胜还是必败。

1<n<4294967295

可以发现当<4294967295的只有2、3、5、7这些质因子的数直观上感觉不是很多,暴力跑一下发现只有6k+个。

然后我们可以用2、3、5、7的质因数个数来表示一个数,然后暴力sg一波~

需要注意的是这里是p>=n的人赢...不过没关系就当做是输吧,输出的时候再反过来。

//By zzq
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
using namespace std;
typedef long long ll;
ll n;
map<ll,int> sgs;
int sg(ll cur)
{
    if(cur>=n) return 0;
    int& mp=sgs[cur];
    if(mp) return mp-1;
    bool ff[6700];
    memset(ff,0,sizeof(ff));
    for(int i=2;i<=9;i++) ff[sg(cur*i)]=1;
    for(int p=0;;p++)
    {
        if(!ff[p]) {mp=p+1; return p;}
    }
}
int main()
{
    while(cin>>n)
    {
        sgs.clear();
        int ans=sg(1);
        if(ans) puts("Stan wins."); else puts("Ollie wins.");
    }
}

例5 bzoj1188

还是n堆石子,这回要求每次选出3个数i,j,k,1<=i<j<=k<=n,i堆中至少要有一个石子,那么在i堆中取出一个石子,在j、k堆中各放入一个,当j=k时就在j堆中放入两个,无法选出的人就输了。

求是否有必胜策略及必胜策略下第一步字典序最小的走法(i,j,k)。

1<n<=21,1<=每堆石子数<=10000。

我们可以发现一些性质,题目只和每堆石子的奇偶性有关,所以我们可以把每堆石子数视为mod 2意义下的。

然后我们还可以发现每堆石子是完全独立的,就是说我们可以用每一堆的异或在一起得到总的sg值。

发现这两个性质以后就比较可做了。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#define SZ 666666
int T,n,a[SZ],sg[SZ];
bool gg[SZ];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int shit=1;
        while(shit<=n) shit*=2;
        shit*=2;
        for(int i=1;i<=n;i++) scanf("%d",a+i), a[i]&=1;
        sg[n]=0;
        for(int i=n-1;i>=1;i--)
        {
            for(int j=0;j<shit;j++) gg[j]=0;
            for(int j=i+1;j<=n;j++)
            {
                for(int k=j;k<=n;k++) gg[sg[j]^sg[k]]=1;
            }
            for(int j=0;;j++)
            {
                if(!gg[j]) {sg[i]=j; break;}
            }
        }
        int ans=0,gs=0;
        for(int i=1;i<=n;i++) if(a[i]) ans^=sg[i];
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                for(int k=j;k<=n;k++)
                {
                    if(ans^sg[i]^sg[j]^sg[k]) continue;
                    ++gs; if(gs==1) printf("%d %d %d\n",i-1,j-1,k-1);
                }
            }
        }
        if(!gs) puts("-1 -1 -1");
        printf("%d\n",gs);
    }
}

这方面题做得也不多,今天就写到这里吧。

时间: 2024-10-12 21:39:04

sg函数与博弈论的相关文章

SG函数-博弈论

b:时间限制:1s空间限制:64M题目大意:有一个图有n个点,且有m条通道连通这n个点,其中第1个点的能量永远恒定为0,初始所有点的能量均为0.第i条通道连接着x_i,y_i两个点,而且它两端点的能量之差不会超过c_i.在整个图中有k个能量源,初始的时候能量源均为休眠状态.现在Alice和Bob玩一个游戏,他们轮流进行操作,每次操作可以选择一个能量源激活,在一个能量源被激活的时候,它将会把当前节点的能量尽可能增大,且其他的点的能量会因为通道的连接同时增大.当当前点的能量达到最大值之后(由于点1的

hdu 1536 S-Nim 博弈论,,求出SG&#39;函数就可以解决

S-Nim Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 4975    Accepted Submission(s): 2141 Problem Description Arthur and his sister Caroll have been playing a game called Nim for some time now

博弈论(SG函数):HNOI 2007 分裂游戏

Description 聪聪和睿睿最近迷上了一款叫做分裂的游戏. 该游戏的规则试: 共有 n 个瓶子, 标号为 0,1,2.....n-1, 第 i 个瓶子中装有 p[i]颗巧克力豆,两个人轮流取豆子,每一轮每人选择 3 个瓶子.标号为 i,j,k, 并要保证 i < j , j < = k 且第 i 个瓶子中至少要有 1 颗巧克力豆,随后这个人从第 i 个瓶子中拿走一颗豆 子并在 j,k 中各放入一粒豆子(j 可能等于 k) .如果轮到某人而他无法按规则取豆子,那么他将输 掉比赛.胜利者可以

【博弈论】【SG函数】bzoj1777 [Usaco2010 Hol]rocks 石头木头

仅有距根节点为奇数距离的节点的石子被移走对答案有贡献,∵即使偶数的石子被移走,迟早会被再移到奇数,而奇数被移走后,不一定能够在移到偶数(到根了). 最多移L个:石子数模(L+1),比较显然,也可以自己跑一跑奇数层的SG函数. #include<cstdio> using namespace std; #define N 10001 int en,v[N],first[N],next[N]; void AddEdge(int U,int V) { v[++en]=V; next[en]=firs

【博弈论】【SG函数】hdu1848 Fibonacci again and again

某个状态的SG函数被定义为 除该状态能一步转移到的状态的SG值以外的最小非负整数. 有如下性质:从SG值为x的状态出发,可以转移到SG值为0,1,...,x-1的状态. 不论SG值增加与否,我们都可以将当前所有子游戏的SG值异或起来从而判断胜负状态. 常采用记忆化搜索来计算SG函数. #include<cstdio> #include<set> #include<cstring> using namespace std; int fib[1001],a[3],SG[10

[BZOJ 1874] [BeiJing2009 WinterCamp] 取石子游戏 【博弈论 | SG函数】

题目链接:BZOJ - 1874 题目分析 这个是一种组合游戏,是许多单个SG游戏的和. 就是指,总的游戏由许多单个SG游戏组合而成,每个SG游戏(也就是每一堆石子)之间互不干扰,每次从所有的单个游戏中选一个进行决策,如果所有单个游戏都无法决策,游戏失败. 有一个结论,SG(A + B + C ... ) = SG(A)^SG(B)^SG(C) ... 这道题每堆石子不超过 1000 , 所以可以把 [0, 1000] 的 SG 值暴力求出来,使用最原始的 SG 函数的定义, SG(u) = m

【博弈论】【SG函数】poj2311 Cutting Game

由于异或运算满足结合律,我们把当前状态的SG函数定义为 它所能切割成的所有纸片对的两两异或和之外的最小非负整数. #include<cstdio> #include<set> #include<cstring> using namespace std; int n,m,SG[201][201]; int sg(int x,int y) { if(SG[x][y]!=-1) return SG[x][y]; set<int>S; for(int i=2;i&l

博弈论SG函数

SG(x)=mex(S),S是x的后继状态的SG函数集合,mex(S)表示不在S内的最小非负整数.如果为0就是必败状态,否则就是必胜状态. 这道题目和Nim差不多,一共有两群熊猫,依次分析,最后异或即可. 1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 #include<vector> 6 #include<queue&g

【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans

一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间.问你是否先手必胜. 一个杯子里的豆子全都等价的,因为sg函数是异或起来的值,所以一个杯子里如果有偶数个豆子,就没有意义. 用sg(i)表示i杯子中的豆子的sg值,sg(i)就是其所能移动到的那段杯子的区间的sg值的mex(未出现的最小非负整数).可以用线段树去做.是经典问题. 由于每次看似是后缀询问,实则是全局询问,故而主席树完全是多余的. 回顾一下区间m