• 最浅谈的SG函数


    【更新】

    Nim游戏的经验:

    每次最多取m个——%(m+1)

    阶梯nim——奇数位无视,看偶数位互相独立,成一堆一堆的石子

    既然被征召去汇总算法。。那么挑个简单点的SG函数好了。。

    介绍:{

      SG函数是解Nim游戏的一个很好的。。。'思路'么?

      Nim游戏是博弈论的一个经典模型,指两个人轮流操作,且双方的操作条件一样(如:中国象棋就不是,因为先手只能动黑子,不能动红子),不能操作的人输(大多是这样。。)

      。。在这个前提下,一个'当前局面'就可以用某个SG值来表示。

    }

    简单的性质:

    1、构成DAG  每个节点代表游戏的一个状态,其SG值,定义为其后继的SG值中没有出现的最小的自然数。 若一个状态不可再操作(SG的游戏中通常定义为输),那么没有出现的最小自然数就是0。

    2、单个游戏博弈:先手必胜要求【

          根节点SG为正,先手从根节点出发

          每一回合

          先手:每次走向任一SG为0的后继

          后手:要么输了(即面对无法操作 没有后继的状态),要么就只能再走到一个SG为正的点,又回到之前的情形

        】    所以单个游戏的SG值为其初始状态即根节点的SG。

        多个游戏一起博弈:先手必胜要求【

            每一个游戏当前的SG值 亦或和为正,(初始时“当前”就是每个游戏的根) ,以下将当前亦或和设为x

          每一回合

          先手:选择一个游戏i,设其当前走到的点u的SG值为SG[u],选择时要求其满足x^SG[u]<SG[u],这样就一定能走到u的一个后继v,SG[v]=SG[u]^x;

            这样 x就变成了x^SG[u]^(SG[u]^x)=0;

              {

                简单说明①:一定存在这样的u。  因为x为所有SG[i]的亦或和,那么必有一个游戏i当前的SG值得二进制最高位1与x的最高位1相同,则其亦或x之后最高位变0,显然变小了。

                简单说明②:为什么要求x^SG[u]<SG[u],不可以走向大于的吗?  毕竟u的后继不一定都小于u,所以的确没这个必要。但是通过说明1,已经证明了一定可以找到SG[v]小于SG[u]的v。

                   而且,并不是所有u都有 SG值比他大的后继,所以为了思考SG问题方便,要求不能走向 比u的SG值大的后继v。

              }

          后手:由于不能操作的 即输了的局面的SG亦或和为0, 所以只有后手会(终将会) 面临这个局面。(因为,每一步总有某一个游戏的状态 沿着其DAG往下走了一步)

            而后手不能操作SG值已经为0的游戏, 只能操作SG值尚为正的游戏,假设这会使游戏i的SG从a变成b,  则x会^=a^b。  而a!=b所以x一定不会依然是0。这样轮到先手又变回原局面

        】

    tip1 若不能直接想出SG计算(或者说证明)的方法,实际上打表找规律才是正解。

    tip2 有很多SG游戏都是分成好多个子问题,将子问题SG亦或起来求总问题SG的。 时刻记得这个性质。

    随便挑道题目吧。

    BZOJ4035[HAOI2015]数组游戏 ——tip1 和tip2都能适用于这道题;

    【题解要写详细真的比做题要nan!!!】

    tip1的话。。。。直接暴力dfs求每个状态的SG值,设白格为1,黑格为0,举例 初始数组为01010。肉眼观察发现: 若两个数组的长度相同,则两个数组亦或 得到的新数组 SG值为这两个数组的SG亦或和。

      即,在n固定的情况下,SG[x]^SG[y]==SG[x^y]  (这里x,y是二进制转成的十进制)

    tip2:【

         通过tip2的思路来证明tip1找到的规律。

         上面这个规律,等价于,一个01数组的SG值,为其中每一个1的SG值亦或和(如:01011的SG==01000、00010、00001的SG亦或和)

        也就是每个白子都是一个子问题,把每个白子的局面翻转成黑即为胜。则多个白子合成的局面等于这些子游戏的SG亦或和

        当一个子游戏会影响另一个子游戏时由于是亦或的,所以对SG值的影响会抵消。

       】

            那么。为什么将游戏x^y分成x和y两个子游戏共同进行博弈,SG值依然是正确的?

       【

           设游戏A 是由x和y构成的子游戏, 游戏B是局面为x^y的单个游戏。

          证明  将游戏x^y分成x和y两个子游戏互不干扰的进行博弈,SG值依然是正确的。

            相当于要证明游戏A中不管是对x还是对y的操作在游戏B中有着对应的操作,这样 A和B  SG值的变化才能始终是一样的

          。

          。

          现在先手对局面x(考虑y和考虑x等价)的这个子游戏进行一次操作 如:010001翻为000101。

          考虑其中被翻转的首位置k:

            显然位置k原先是1,

                  若y中k原先是0, 则  在游戏B中,k是1, 显然在B中可以有一样的操作

                  若y中k原先是1,则  游戏B中k是0,不可翻转,怎么办?实际上,先手不会这么操作,因为后手可以对y做一样的操作,使SG值又变回来。 所以先手会对其他白子操作,后手最终会不得不进行这种操作,这时,由先手对y做一样的操作,保持必胜的局面。 那么很显然 这一部分的操作,对游戏B就不会有任何影响了。            

       】

    那么剩下的问题就是求出每单个白子的SG值了:

    首先输入了n,既然确定了n,那么:

    用f[i]表示可以翻i个的点的SG值,  即:  0001000000和000010000 这里的1都最多能翻2个位置,那么它们的SG值都记录在f[2]

    用g[i]表示位置i的SG值,  即: 0100000000 ,它的SG值记录在g[2]。

    f[i]=g[n div i],  但是由于n<=10^9,所以,f,g都只记录到下标sqrt(n),两个拼起来就可以O(1) 求出每个位置的SG值了。

    对于每个f[i]或g[i] 怎么求:对商分块,这样复杂度就是 [求f[i]:sqrt(i)]   [求g[i]: sqrt(n/i)]。

    当然预处理是 O(n^0.5  *  n^0.5) 显然两个根号都不会跑满。O(2)下,预处理只要半秒。还是有点慢慢啊。

     1 #pragma GCC optimize(2)
     2 #include <bits/stdc++.h>
     3 using namespace std;
     4 int N,n,m,k,x,s,f[32005],g[32005];
     5 int get(int w){
     6     if (w<=N) {if (f[w]!=-1) return f[w];} else
     7         if (g[n/w]!=-1) return g[n/w];
     8     int d[128]; for (int i=1;i<128;++i) d[i]=0;
     9     for (int x=2,y,z=0;x<=w;x=w/(w/x)+1){
    10         y=get(w/x); d[z^y]=1;
    11         if (!(w/(w/x)-x&1)) z^=y;
    12     }
    13     for (int i=1;i<128;++i) if (!d[i]) return i;
    14 }
    15 int main(){
    16     scanf("%d%d",&n,&m); 
    17     N=ceil(sqrt(n));
    18     memset(f,-1,sizeof f); f[1]=1;
    19     memset(g,-1,sizeof g);
    20     for (int i=2;i<=N;++i) f[i]=get(i);
    21     for (int i=n/N-1;i>0;--i) g[i]=get(n/i);
    22     while (m--){
    23         scanf("%d",&k); s=0;
    24         for (int i=1;i<=k;++i) scanf("%d",&x),s^=get(n/x);
    25         s?puts("Yes"):puts("No");
    26     }
    27     return 0;
    28 }
    乌鸦行
  • 相关阅读:
    C++11 lambda表达式(lambda expression)
    win 10 relog.exe 下载地址
    检测闩锁/自旋锁争用
    关于sql 锁和并发的一些记录
    FAST number_rows 意义解释
    网站实施SEO的步骤
    搜索引擎高级搜索指令浅析
    关于遇到高并发时候的一些总结
    Autofac 设置方法拦截器的两种方式
    C# MVC 进入Action 方法之后怎么使用MVC参数验证模型
  • 原文地址:https://www.cnblogs.com/cyz666/p/7020229.html
Copyright © 2020-2023  润新知