删除子集的游戏算法[HW / Study]

时间:2013-02-08 19:30:27

标签: algorithm bit-manipulation

我们遇到了一个问题,我已将其简化为以下内容: 您将获得一个包含所有值的二进制数(例如11111)和一组长度相同的二进制数(00101,10000,01100,00100,11100)。 有两名球员A& B.在每个转弯处,玩家可以从主二进制数(11111)中减去任何一个较小的数字,使得2的二进制AND是较小的数字。然后下一个玩家可以从结果中减去,依此类推。不能再减去的玩家输了。 例如

   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;
            }

        }
    }
}

2 个答案:

答案 0 :(得分:3)

我不知道它是否会对你有帮助(你讲过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)&gt; 0然后开始位置是获胜位置(存在转向位置x使得g(x)= 0),所以第一个玩家获胜。

抱歉英语不好,希望能有所帮助

答案 1 :(得分:0)

根据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";