【问题标题】:Game Algorithm for Removing Subsets [HW/Study]去除子集的博弈算法 [硬件/研究]
【发布时间】:2013-02-08 19:30:27
【问题描述】:

我们遇到了一个问题,我将其简化为以下问题: 你会得到一个全为1的二进制数(例如11111)和一组相同长度的二进制数(00101、10000、01100、00100、11100)。 有两个玩家 A 和 B。在每一轮,玩家可以从主二进制数 (11111) 中减去任何一个较小的数字,这样 2 的二进制与就是较小的数字。然后下一个玩家可以从结果中减去,依此类推。不能再减去的玩家就输了。 例如。

   A         B       
 11111     11010   // Now A cannot subtract 
-00101    -10000   // anymore in the next turn. 
-------   ------   // So B wins the game.
 11010     01010
-------   ------

如果两个玩家都发挥最佳(为他们的胜利做出最佳选择),我必须找出给定二进制数字组合的哪个玩家获胜。

我尝试了 O(n^2) 的方法,但有更快的方法吗?

编辑: O(n^2) :其中 n 是状态数。对于长度为 6 (111111) 的二进制数,可能有 2^6 种状态。所以我的复杂度是 O((2^6)^2)。

编辑: 我的代码生成所有可能的状态:

void makeAllStates() /* Bottom Up Approach. Starting from 00000 and going to 11111 */
{
    // bool states[i] : True if state[i] is a winning position.
    // bool isWord[i] : True if the given state matches a smaller number. (eg. If the main number has been reduced to 10110 and there is a smaller number 10110, then isWord[i] is true.
    // bool visited[i] : True If the given state has been visited  
    // int statecount : Total number of states
    int temp;
    for(int xx=1;xx<stateCount;xx++)
    {
        for(int yy=1;yy<stateCount;yy++)
        {
            if(xx&yy)
                continue;
            if(!(isWord[xx] || isWord[yy]))
                continue;
            if(!visited[yy])
                continue;
            temp = xx^yy;
            if(isWord[temp])
                continue;
            if(states[temp])
                continue;
            if(isWord[xx] && isWord[yy])
                states[temp] = false;
            else
            {
                if(isWord[xx])
                    states[temp] = !states[yy];
                else
                    states[temp] = !states[xx];
            }
            visited[temp] = true;
            if(temp == stateCount-1 && states[temp])
            {
                return;
            }

        }
    }
}

【问题讨论】:

  • 由于减法只能在 AND 是较小的数字时发生,它相当于 XOR 和与较小数字的补码的 ANDing。我不确定这是否有帮助,但它更简单一些。
  • 这听起来和 Nim 很像 en.wikipedia.org/wiki/Nim>,里面有点理论的探讨。
  • @harold :事实上,它可以作为一个 XOR 一起使用并使用 ANDing 进行验证。
  • @vonbrand:是的。但我无法弄清楚我们之间的任何关系。

标签: algorithm bit-manipulation


【解决方案1】:

我不知道它是否对你有帮助(你提到了 O(n^2) 方法,但没有说 N 是什么意思)。尝试公平游戏的通用方法(Sprague-Grundy 理论):

  • 游戏中的位置是你的主号
  • 找出所有“亏损”的头寸(这些头寸你不能再减去任何东西)
  • 对于所有“失去”的位置 x:Grundy 函数 g(x) = 0;
  • 然后,如果您想计算位置 y 的 grundy 函数:找到所有位置 x_1...x_k,这样您就可以从位置 y 转向位置 x_i。 g(y) = mex(g(x_1),...,g(x_k))。 “mex”是“最小排除项”——除 g(x_1),...,g(x_k) 之外的所有最小非负整数。例如,mex(2, 3, 4) = 0,mex(0, 1, 2, 5) = 3,mex(0, 1) = 2,等等。

请注意,您可以递归地考虑每个游戏位置,并且您将考虑位置 x 一次(在计算 g(x) 时),因此该算法与可能位置的数量成 线性关系。 与位置之间可能的回合数,即 O(N*K) 其中 N 是状态数,K 是较小数字集的大小(您可以在游戏中进行回合)

如果g(START_POSITION) = 0,则起始位置为输局,第一个玩家输掉(每回合都会导致一个获胜位置)。如果 g(START_POSITION) > 0 则起始位置为获胜位置(存在转向位置 x 使得 g(x) = 0),因此第一个玩家获胜。

抱歉英语不好,希望对你有帮助

【讨论】:

  • 抱歉信息丢失。现在更新了。这与给我 O(n^2) 时间复杂度的方法相同。将尝试使用您的方法将其减少到 O(n)。
  • 无法将复杂度降低到 O(n),但是使用您的方法,在很大程度上减少了调用次数。在下面添加我的最终代码!
  • 这是我的错,对不起,我认为它与状态数成线性关系,但它的复杂度等于位置图中深度优先搜索的复杂度,即 O(N*K) 有 N - 状态数,K - 较小数字集的大小。刚刚编辑了我的答案。
  • 顺便说一下,sprague-grundy 理论的一个关键点是,如果你把这个游戏概括为:位置是一组主要数字 x_1, x_2, ..., x_k 和每个你可以轮流选择主数 x_i 然后对其进行操作,那么 G(x_1,x_2,...,x_k) 就是 g(x_1) XOR g(x_1) XOR ... XOR g(x_k) ,其中 XOR 是使用整数 g(x_j) grundy 函数按位计算的。每场不偏不倚的博弈(胜负由初始位置决定)都可以归结为 NIM 博弈。
【解决方案2】:

根据 K.Bulatov 的输入,这是最坏情况下时间复杂度为 O(n^2) 的最终代码。剪枝后,调用次数大大减少。 主要功能如下:

//state : The state for which grundy number is being queried.
//visited[i] : If the grundy number of the state has already been calculated.
//wordStates[] : an array with all the smaller numbers stored.
//mex() : "minimal excludant" - the smallest non-negative integer not in array
int grundyNum(int state)
{
    if(visited[state])
        return grundy[state];
    int grundArr[wordStates.size()];
    int loc =0;
    visited[state] = true;
    for(int xx =0;xx<wordStates.size();xx++)
    {
        if((state&wordStates[xx]) == wordStates[xx])
        {
            grundArr[loc] = grundyNum(state^wordStates[xx]);
            loc++;
        }
    }
    grundy[state] =  mex(grundArr,loc);
    return grundy[state];
}

只需使用以下方法调用该函数即可获得胜利:

result = grundy(1111111);
winner = result==0?"B":"A";

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 2013-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多