洛谷Oj-P1290 欧几里德的游戏-SG函数

问题描述:
欧几里德的两个后代Stan和Ollie正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的。给定两个正整数M和N,从Stan开始,从其中较大的一个数,减去较小的数的正整数倍,当然,得到的数不能小于0。然后是Ollie,对刚才得到的数,和M,N中较小的那个数,再进行同样的操作……直到一个人得到了0,他就取得了胜利。下面是他们用(25,7)两个数游戏的过程:
Start:25 7
Stan:11 7
Ollie:4 7
Stan:4 3
Ollie:1 3
Stan:1 0
Stan赢得了游戏的胜利。
现在,假设他们完美地操作,谁会取得胜利呢?
递归爆栈T_T代码:

long long m,n;
bool dfs(long long m,long long n)
{
    if(m == 0 || n == 0)//边界条件
        return true;
    if(m > n)//分情况讨论
    {
        for(int i = 1; ; ++i)//从1倍开始减
        {
            if(m - i * n < 0)
                break;
            if(dfs(n,m - i * n) == true)//如果对手必胜
                return false;//我方必败
        }
    }
    else
    {
        for(int i = 1; ; ++i)//从1倍开始减
        {
            if(n - i * m < 0)
                break;
            if(dfs(m,n - i * m) == true)//如果对手必胜
                return false;//我方必败
        }
    }
    return true;
}
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        cin >> m >> n;
        if(m % n == 0 || n % m == 0)//特判
        {
            puts("Stan wins");
            continue;
        }
        if(dfs(m,n) == true)//搜索
            puts("Ollie wins");
        else
            puts("Stan wins");
    }
    return 0;
}

AC代码,解法①:

int n,m;
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        cin >> m >> n;
        if(m == n)
            puts("Stan wins");
        else
        {
            if((max(m,n) * 1.0) / min(m,n) > (sqrt(5) + 1) / 2)
                puts("Stan wins");
            else
                puts("Ollie wins");
        }
    }
    return 0;
}

AC代码,解法②:

bool solve(int n,int m)
{
    if(m == 0)
        return false;
    if(n / m == 1)
        return !solve(m,n % m);
    else//n / m = 2,3,4,......
        return true;
}
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int n,m;
        cin >> n >> m;
        if(solve(max(n,m),min(n,m)) == true)
            puts("Stan wins");
        else
            puts("Ollie wins");
    }
    return 0;
}

AC代码,解法③:

int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int n,m;
        cin >> n >> m;
        if(n < m)
            swap(n,m);
        if(n / m >= 2)//先手必胜
        {
            puts("Stan wins");
            continue;
        }
        int who = 1;//1代表先手,为Stan
        while(n / m == 1 && n % m != 0)//当循环结束时,有n / m >= 2或者n % m == 0二者情况都是先手必胜
        {
            //进入循环说明mm而不是n>=m是因为循环中有条件
            n = n % m;//只能这么取,或者写成n-m
            swap(n,m);//维护相对大小
            who = -who;
        }
        if(who == 1)
            puts("Stan wins");
        else
            puts("Ollie wins");
    }
    return 0;
}

解决方法:
爆栈那个,就不多说了,太无脑了。
解法①是个结论,不太懂。如果两个数相等,或者两数之比大于 5+12 ,则先手胜,否则后手胜。
解法②的分析如下:
我们可以将两个数字转换为两堆石子,其大小分别为n、m。
这样就把游戏转换为了Nim游戏的变形,于是想到用SG函数的方法来求解
SG函数有两个变量n、m,表示当前的局面。
由于SG函数是通过逆推求解的,所以我们从终止局面开始考虑
不妨设 n>m
当m = 0时,先手必败。此时SG(n,m) = 0,这是SG函数定义中的边界情况
其他情况:SG(n,m) = mex{SG(n - m),SG(n - 2m),......,SG(n - (k - 1)m,m),SG(m,n % m)},k = n / m
n - km = n % m
那么mex函数内的SG值怎么求呢
SG(n - m) = mex{SG(n - 2m),SG(n - 3m),......,SG(m,n % m)}
SG(n - 2m) = mex{SG(n - 3m),SG(n - 4m),......,SG(m,n % m)}
可以看出,除了SG(m,n % m)以外的其他SG函数值都可以由SG(m,n % m)}得来
如果SG(m,n % m) = 0,k = n / m,那么SG(n - (k - 1)m,m) = mex{SG(m,n % m)} = 1
其他SG值分别为2,3,4,5,……均为必胜态,可以简记为1
如果SG(m,n % m) = 1,k = n / m,那么SG(n - (k - 1)m,m) = mex{SG(m,n % m)} = 0
其他SG值仍为2,3,4,5,……
n(k1)mm=nkm+mm=n%m+mm=1
所以对于任意局面的SG(n,m)如果n/m = 1,则SG(n,m)=!SG(m,n % m),否则就是1(简化的)
解法③分析:
这个问题还可以转化为巴什博奕,因为减去的数(相当于拿走的石子)是有上限的,还是转化为取石子游戏来说吧。
不妨设 n>m
至少取走m个石子,至多取走k * m,k = n / m个石子
不一样的地方在于取的石子数并不是连续的,而是较小的数m的整数倍,不能直接套巴什博奕的结论
但是我们仍然可以利用巴什博奕的思想,当n >= 2m时,来看状态(n%m,m)
如果(n%m,m)为P局面,则先手只需将状态(n,m)转为(n%m,m),必胜
如果(n%m,m)为N局面,则先手将状态(n,m)转移为(n % m + m,m),则后手只能将状态转移为(n%m,m),N局面回到先手的手中,先手必胜
总之,当n >= 2m时,先手必胜
否则,只能从n中取走m,然后利用上述结论进行循环或者递归。
还要判断n%m==0的情况

你可能感兴趣的:(博弈)