zoukankan      html  css  js  c++  java
  • 每天一道博弈论之“*游戏”(HNOI2007)

       题目链接:https://www.luogu.org/problemnew/show/P3185

      题解:

      首先这道题第一眼望过去好像不是很简单qwq,那么我们先尝试看能不能发现一些特殊性质。

      我们先从后手相消的原则去看,则可以发现若一个瓶子中有偶数个的话,那么这个瓶子是没有意义的。因为后手可以完全复制先手的操作,则该瓶子内减去偶数个巧克力豆还是偶数个,新增的两个瓶子内也都是增加了偶数个。

      于是我们就只考虑奇数个的瓶子。而且明显只要当成1就可以了,因为取掉1之后就是偶数,就可以当做不存在了。那么原序列就变成了一个01序列。、

      假如我们了解过multi-nim模型的话我们就可以发现其实剩下的操作与multi-nim是很像的。

     什么是multi-nim呢?

      n堆石子,两种操作,其一为在一堆石子中拿走任意个(>0),其二为把一堆石子分成两堆,这两堆石子总数等于原石子数,且都是正整数。不能操作时输。

      可以发现每堆石子是独立的。一堆石子数为x的sg值为mex{sg[y],sg[i]^sg[j]}(0<=y<x,i+j==x,i>0,j>0)

      我们回到原题。

      我们可以观察到每个1所对应的游戏状态是独立的,那么我们算出每个1的sg值再用sg定理即可解决。我们可以把01序列中1的位置对应到multi-nim中的值。比如样例中序列(1,0,1,5000),转换成01序列为(1,0,1)(最后一位不能移动,不需考虑),那么其对应的游戏状态为(1,3),只不过这时与multi-nim不同的一点就是此时一个数分成的两个数之和不一定要等于原数,而且可以为零。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define LL long long
    #define RI register int
    using namespace std;
    const int INF = 0x7ffffff ;
    
    inline int read() {
        int k = 0 , f = 1 ; char c = getchar() ;
        for( ; !isdigit(c) ; c = getchar())
          if(c == '-') f = -1 ;
        for( ; isdigit(c) ; c = getchar())
          k = k*10 + c-'0' ;
        return k*f ;
    }
    
    int n ; int sg[23], a[23] ;
    
    int dfs(int x) {
        if(sg[x]) return sg[x] ;
        bool v[100] = {0} ;
        for(int i=x+1;i<=n;i++)
          for(int j=i;j<=n;j++)
            v[dfs(i)^dfs(j)] = 1 ;
        for(int i=0; ;i++)
          if(!v[i]) {
              sg[x] = i ; return i ;
          }
    }
    
    int main() {
        int t = read() ;
        while(t--) {
            n = read() ; memset(sg,0,sizeof(sg)) ;
            for(int i=1;i<=n;i++) a[i] = read() ;
            int res = 0 ;
            for(int i=1;i<n;i++) if(a[i]&1) res ^= dfs(i) ;
            int tot = 0 ;
            for(int i=1;i<n;i++) {
    //            if(!(a[i]&1)) continue ;  // 虽然我们在转换成multi-nim游戏后不用考虑那些偶数点
                                          // 但从方案的角度考虑先移偶数也可能会到达必败态 
                for(int j=i+1;j<=n;j++) 
                  for(int k=j;k<=n;k++) {
                      if(!(dfs(j)^dfs(k)^dfs(i)^res)) {  // 当该位原本为奇数时,dfs(i)^res即删去原位的i
                                                       // 再亦或上dfs(j)^dfs(k)即在i为与j位加上1
                                                       // 当该位原本为偶数时,res表示原游戏sg值
                                                       // 移动导致i,j,k三个位置都发生奇偶变化,即新增三个游戏,所以要亦或上三个游戏的sg值 
                          tot ++ ;
                          if(tot == 1) printf("%d %d %d
    ",i-1,j-1,k-1) ;
                      }
                  }
            }
            if(!tot) printf("-1 -1 -1
    ") ;
            printf("%d
    ",tot) ;
        }
        return 0 ;
    }
    View Code

      错误原因:一开始没有考虑到虽然对于胜负来说偶数无所谓,但对于方案来说还是可以移动偶数的。所以只要把那句跳过偶数的注释掉就ok了。

     (博弈论主要是个人的思考与理解,很难讲明白,本博客只希望其中的某几句话能激发大家的灵感即可)

     ——end ;

  • 相关阅读:
    【WPF】Blend for Visual Studio 2013 入门
    【c#】获取CPU序列号及遇到的问题
    【GIT】学习笔记
    【备份】一些留待学习的好网站
    【VS】无法折叠所有方法的问题 VS2013
    【概念】XML
    配置JDK
    TCP/IP Wireshark监听 及错误代码
    软件比较
    湖南省专升本
  • 原文地址:https://www.cnblogs.com/zub23333/p/8548182.html
Copyright © 2011-2022 走看看