【问题标题】:beauty of a binary number game二进制数游戏之美
【发布时间】:2014-05-27 03:07:14
【问题描述】:

这是一个众所周知的问题(类似问题:number of setbits in a number and a game based on setbits 但答案不清楚):

数字 X 的美妙之处在于二进制中 1 的数量 X 的表示。两个玩家正在玩游戏。有一个数 n 写在黑板上。游戏玩法如下:

每次玩家选择一个整数 (0

第一个玩家开始游戏,他们交替轮流。 知道两个玩家都打得最佳,必须指定 赢家。

现在我想出的解决方案是这样的:

向右移动 1 位,就是将数字减去 2^p,其中(p = 移动到的位的位置 - 1)。示例:11001 --> 25 现在如果我将其更改为 10101 ---> 21 ( 25-(2^2))

玩家不能在 1 轮中进行 2 次或更多这样的右移(不是程序化右移),因为它们不能求和 2 的幂。所以玩家只能将设置的位移动到某个位置每轮只有一次。这意味着只能有 R 轮,其中 R 是设置位可以移动到更正确位置的次数。因此,如果 R 是奇数,获胜者将永远是第一名,如果 R 是偶数,获胜者将永远是第二名。

Original#: 101001 41
after 1st: 11001 25 (41-16)
after 2nd: 10101 21 (25-4)
after 1st:  1101 13 (21-8)
after 2nd:  1011 11 (13-2)
after 1st:   111  7 (11-4) --> the game will end at this point

我不确定该方法的正确性,这是正确的吗?还是我错过了什么大事?

【问题讨论】:

    标签: algorithm binary bit-manipulation bit


    【解决方案1】:

    您的方法是正确的。这里要观察的是,同样如您给出的示例所示,当所有 1 都在最低有效位时,游戏结束。所以我们基本上需要计算需要多少次交换才能使零到达最高有效位。

    举个例子,假设游戏开始的初始数字是12,游戏状态如下:

    Initial state 1100 (12) -> 
    A makes move 1010 (10) -> 
    B makes move 1001 (9) -> 
    A makes move 0101 (5) -> 
    B makes 0011 (3)
    A cannot move further and B wins
    

    这可以通过编程方式(java程序v7)实现为

    public int identifyWinner (int n) {
    
        int total = 0, numZeros = 0;
    
        while (n != 0) {
            if ((n & 0x01) == 1) {
                total += numZeros;
            } else {
                numZeros++;
            }
            n >>= 1;
        }
    
        return (total & 0b1) == 1 ? 1 : 2;
    }
    

    还要注意,即使玩家有多种选择可以进行下一步,如下图所示,结果不会改变,尽管导致结果的中间变化可能会改变。

    让我们再次以初始数字 12 为例来看看状态流

    Initial state 1100 (12) -> 
    A makes move 1010 (10) -> 
    (B here has multiple choices) B makes move 0110 (6)
    A makes move 0101 (5) -> 
    B makes 0011 (3) 
    A cannot move further and B wins
    

    A 不能进一步移动,因为没有 k (k >=0 和 n

    以 41 为起点也可以多选,但 A 永远赢 (41(S) -> 37(A) -> 35(B) -> 19(A) -> 11(B) - > 7(A))。

    希望对你有帮助!

    【讨论】:

      【解决方案2】:

      是的,如果右边有一个 0,每转一个 1 就可以向右移动。

      但是,不,移动的数量与零的数量无关。反例:

      101 (1 possible move)
      

      110 (2 possible moves)
      

      【讨论】:

      • 啊,终于有回应了!! :) 谢谢。是的,这就是我所说的每个设置位都可以移动到正确位置的意思。 “这意味着只能有 r 轮,其中 r 是设置位可以移动到正确位置的次数”。在 110 示例中,第一轮将更改为 101,下一轮将更改为 011。如果问题需要改写,请告诉我。
      • @maver1k 您写道:“因此,如果有奇数个 0,获胜者将永远是第一个玩家,如果有偶数个 0,获胜者将永远是第二个玩家。”,这就是我的回答。跨度>
      【解决方案3】:

      游戏中的移动数是每个 0 左侧的总 1 的总和。(或者相反,每个 1 右侧的总 0 的总和。)
      (即 11000 有 2 + 2 + 2 = 6 步,但 10100 有 1 + 2 + 2 = 5 步,因为一个 0 的右边少了一个 1)

      如果游戏中的总步数为奇数,则游戏的获胜者将是第一个玩家,如果游戏中的移动数是偶数,则将是第二个玩家。

      证明:

      在任何给定的移动中,玩家必须选择对应的位 一个 0 紧跟在一个 1 的右边。否则,总数 如果选择了对应于不同 0 的位,则 1 会增加, 如果选择了对应于 1 的位,则会减少。这样的举动 将导致相应所选位右侧的 1 向右移动一个位置。

      鉴于这一观察,每个 1 必须向右移动每一个 0;它每移动一个 0 通过消耗一招。请注意,无论选择如何 玩家在任何给定的移动上,游戏中移动的总数 保持不变。

      由于 Harshdeep 已经发布了一个 correct solution 循环遍历每个位(O(n) 解决方案),我将发布一个优化的分而治之 O(log(n)) 解决方案(在 C/C++ 中),让人想起similar algorithm 计算汉明权重。当然这里用 Big-Oh 来描述算法有点可疑,因为比特数是恒定的。

      我已经验证所有 32 位无符号整数的以下代码给出的结果与线性算法相同。此代码在我的机器上按顺序在 45 秒内运行所有值,而线性代码需要 6 分 45 秒。

      代码:

      bool FastP1Win(unsigned n) {
            unsigned t;
      
            // lo: 0/1 count parity
            // hi: move count parity
            // 00 -> 00 : 00 >>1-> 00 &01-> 00 ; 00 |00-> 00 ; 00 &01-> 00 &00-> 00 *11-> 00 ^00-> 00
            // 01 -> 01 : 01 >>1-> 00 &01-> 00 ; 01 |00-> 01 ; 01 &01-> 01 &00-> 00 *11-> 00 ^01-> 01
            // 10 -> 11 : 10 >>1-> 01 &01-> 01 ; 10 |01-> 11 ; 10 &01-> 00 &01-> 00 *11-> 00 ^11-> 11
            // 11 -> 00 : 11 >>1-> 01 &01-> 01 ; 11 |01-> 11 ; 11 &01-> 01 &01-> 01 *11-> 11 ^11-> 00
            t  = (n >> 1) & 0x55555555;
            n  = (n | t) ^ ((n & t & 0x55555555) * 0x3);
      
            t  = n << 2;                          // move every right 2-bit solution to line up with the every left 2-bit solution
            n ^= ((n & t & 0x44444444) << 1) ^ t; // merge the right 2-bit solution into the left 2-bit solution
            t  = (n << 4);                        // move every right 4-bit solution to line up with the every left 4-bit solution
            n ^= ((n & t & 0x40404040) << 1) ^ t; // merge the right 4-bit solution into the left 4-bit solution (stored in the high 2 bits of every 4 bits)
            t  = n << 8;                          // move every right 8-bit solution to line up with the every left 8-bit solution
            n ^= ((n & t & 0x40004000) << 1) ^ t; // merge the right 8-bit solution into the left 8-bit solution (stored in the high 2 bits of every 8 bits)
            t  = n << 16;                         // move every right 16-bit solution to line up with the every left 16-bit solution
            n ^= ((n & t) << 1) ^ t;              // merge the right 16-bit solution into the left 16-bit solution (stored in the high 2 bits of every 16 bits)
            return (int)n < 0;                    // return the parity of the move count of the overall solution (now stored in the sign bit)
      }
      

      说明:
      要找到游戏中的移动数,可以将问题分成更小的部分并将这些部分组合起来。必须跟踪任何给定棋子中 0 的数量,以及任何给定棋子中的移动次数。

      例如,如果我们将问题分成两个 16 位的部分,则以下等式表示解决方案的组合:

      totalmoves = leftmoves + rightmoves + (rightzeros * (16 - leftzeros)); // 16 - leftzeros yields the leftones count
      

      由于我们不关心总移动,只要判断该值是偶数还是奇数(奇偶校验),我们只需要跟踪奇偶校验。

      这是加法奇偶性的真值表:

      even + even = even
      even + odd  = odd
      odd  + even = odd
      odd  + odd  = even
      

      给定上面的真值表,加法的奇偶性可以用异或来表示。

      以及乘法奇偶性的真值表:

      even * even = even
      even * odd  = even
      odd  * even = even
      odd  * odd  = odd
      

      给定上面的真值表,乘法的奇偶性可以用AND来表示。

      如果我们将问题分成大小相等的部分,那么零计数和一计数的奇偶校验将始终相等,我们无需单独跟踪或计算它们。

      在算法的任何给定阶段,我们都需要知道零/一计数的奇偶性,以及该解决方案中移动数的奇偶性。这需要两位。因此,让我们在解决方案中转换每两位,使高位成为移动计数奇偶校验,低位成为零/一计数奇偶校验。

      这是通过这个计算完成的:

      unsigned t;
      t  = (n >> 1) & 0x55555555;
      n  = (n | t) ^ ((n & t & 0x55555555) * 0x3);
      

      从这里我们将每个相邻的 2 位解决方案组合成一个 4 位解决方案(使用 &amp; 进行乘法,使用 ^ 进行加法,以及上述关系),然后将每个相邻的 4 位解决方案组合成一个 8位解,然后每个相邻的 8 位解变成一个 16 位解,最后每个相邻的 16 位解变成一个 32 位解。

      最后,只返回移动次数的奇偶性存储在第二个最低有效位中。

      【讨论】:

      • 仔细观察其他一些解决方案,Harshdeep 的代码是正确的。如果要优化它,可以使用 256 大小的查找表一次处理 1 个字节。 (或大小为 16 的查找表,一次处理 4 位)
      • 经过进一步思考后,我意识到不需要查找表。由于您只需要跟踪移动计数的奇偶性,而不是实际的移动计数,因此可以使用其他方法更有效地完成。解决方案现已在上面发布。
      猜你喜欢
      • 2011-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多