博弈论学习小结&HDU 4664#by zh

        上次多校有道博弈的题,完全不知道怎么做,赛后艾神说用SG函数然后异或什么的,但是当时根本不知道是什么东西,于是就看了一下博弈,感觉还是不太难的。这几天做了一些博弈入门的题目,在这里总结一下。

        以取石子游戏为例,一堆石子有n个,每次每人最多取k个,先取光者胜。对于游戏中的任意一种状态,只有两种可能,先手必胜,或先手必败。我们称先手必胜的点为N点,先手必败的点为P点,则对于一个确定的n和k而言,先手者的胜负是确定了的,只要最初的状态为N点则,先手必胜,否则先手必败。给出结论:若一个点可以一步到达一个P点,则该点为N点;若一个点在一步之内无论怎么走,都会到达一个N点,则该点为P点。利用上面的结论(我觉得还是很好理解的),我们可以确定任意一个状态的胜负情况。定义函数SG(x),SG(x)就是去掉所有能到达状态的SG值之后的最小整数,若SG值为0,则当前状态必败。如果有多个子游戏,那总SG值就是SG(X1)^SG(X2)^...^SG(Xn),那么对于多个游戏,我们把SG值预处理一下,再异或出最终结果就可以了。贴出我学习的博客链接http://blog.sina.com.cn/s/blog_83d1d5c70100y9yd.html

HDU 4664给了N个平面,平面上有N个点,构成一个凸包,每次可以选取两个点把他们连接起来,如果某个平面先构成了一个三角形那么这个平面的游戏就结束了,如果某人无法对任意一个平面进行操作,那么他就输了,输出谁有必胜策略。题目给了N个平面,就相当于N个子游戏,那么我们只要求出一个平面下的情况,异或就好了。对于一个平面的情况,如果某两个点之前已经被连了一条线,那么这两个点都不能再去连其他的点,因为这样做必败,而题目中的线不能有相交,那么对一个平面最初的操作就把这个平面分成了两部分,初态的SG值,就是分成两部分之后的SG值异或,那么我们枚举一下选两个点连线后左边和右边各有多少点,把他们异或一下就是子状态的SG值,因为SG[0]=SG[1]=0,那么我们从前向后推就可以了,鉴于这个题的ai非常大,我们大胆猜想SG值一定有循环,打表发现SG值在68以后,每34一循环,这样的话任意一个状态的SG值也就知道了,异或输出答案即可。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int sg[1000];
bool vis[1000];
int main()
{
    sg[0]=0;
    sg[1]=0;
    for(int i=2;i<1000;i++)
    {
        memset(vis,0,sizeof(vis));
        for(int j=0;j<=(i-2)/2;j++)
        {
            vis[sg[j]^sg[i-2-j]]=true;
        }
        for(int j=0;j<=i;j++)
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
    }
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,ans=0;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            int temp;
            scanf("%d",&temp);
            if(temp>34*2)
            {
                temp=(temp%34)+34*2;
            }
            ans^=sg[temp];
        }
        if(ans)
            puts("Carol");
        else puts("Dave");
    }
}

你可能感兴趣的:(博弈论学习小结&HDU 4664#by zh)