二进制数游戏的美

时间:2014-04-13 02:13:15

标签: algorithm binary bit-manipulation bit

这是一个相当已知的问题(类似的问题:number of setbits in a number and a game based on setbits但回答不清楚):

  

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

     

每次玩家选择整数(0 <= k)以使得2 ^ k为   小于n和(n-2 ^ k)同n一样美。然后从中移除n   黑板,取而代之的是n-2 ^ k。无法继续的玩家   游戏(没有满足约束的k)失去了   游戏。

     

第一个玩家开始游戏,他们轮流转。   知道两个玩家都达到最佳状态必须指定   赢家。

现在我提出的解决方案是:

向右移动1位,将数字减去2 ^ p,其中(p =位移动到-1的位置)。示例:11001 - &gt; 25如果我把它改成10101 ---&gt; 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

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

3 个答案:

答案 0 :(得分:2)

您的方法走在正确的轨道上。这里要做的观察是,正如你给出的例子所示,当所有的都在最低位时,游戏结束。所以我们基本上需要计算我们需要多少次交换才能使零点变为最重要的位。

让我们举一个例子,比如游戏开始时的初始数字是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 <2 ** k因此k = 0,1这是这里唯一合理的选择)n-2 ^ k与n具有相同的美感B胜。

多种选择也是可能的,41也是起点,但A总是赢(41(S) - > 37(A) - > 35(B) - > 19(A) - > 11 (B) - > 7(A))。

希望它有所帮助!

答案 1 :(得分:0)

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

但是,不,移动次数与零个数无关。反:

101 (1 possible move)

110 (2 possible moves)

答案 2 :(得分:0)

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

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

  

证明:

     

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

     

鉴于此观察,每个   1必须向右移动0;并且每移动0   通过消耗一个动作。请注意,无论选择如何   玩家在任何给定的移动中,在游戏中移动的总数   仍然是固定的。

由于Harshdeep已经在每个位(O(n)解决方案)上发布correct solution循环,我将发布优化的划分并征服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

鉴于上述真值表,加法奇偶校验可用XOR表示。

乘法奇偶校验的真值表:

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位解决方案(使用&进行乘法,^进行加法,然后使用上述关系),然后是每个相邻的4位解决方案解决方案转换为8位解决方案,然后将每个相邻的8位解决方案转换为16位解决方案,最后将每个相邻的16位解决方案转换为32位解决方案。

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