暑训day2 博弈论hhhh

今天的知识简直不要太有趣——博弈论(聪明的找规律)

重中之重 Nim博弈

(网上很多博客在讲,都很详细)

今天的题目都相对来说比较有(容)意(易)思
A 典型的Nim博弈+简单的sg打表即可
B&C 巴什博弈 注意对(m+1)取模,m+1什么你懂得
D 结果极其弱智,但需要思考的地方还很多,估计这种游戏策略就是先发制人吧
E 同A,只不过sg打表稍微复杂一些,这里引入两个模板
递推形式

//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//hash[]:mex{}
int f[N],sg[N],hash[N];     
void getSG(int n)
{
    int i,j;
    memset(sg,0,sizeof(sg));
    for(i=1;i<=n;i++)
    {
        memset(hash,0,sizeof(hash));
        for(j=1;f[j]<=i;j++)
            hash[sg[i-f[j]]]=1;
        for(j=0;j<=n;j++)    //求mes{}中未出现的最小的非负整数
        {
            if(hash[j]==0)
            {
                sg[i]=j;
                break;
            }
        }
    }
}

dfs形式,处理数据较大

//DFS
//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[110],sg[10010],n;
int SG_dfs(int x)
{
    int i;
    if(sg[x]!=-1)
        return sg[x];
    bool vis[110];
    memset(vis,0,sizeof(vis));
    for(i=0;i<n;i++)
    {
        if(x>=s[i])
        {
            SG_dfs(x-s[i]);
            vis[sg[x-s[i]]]=1;
        }
    }
    int e;
    for(i=0;;i++)
        if(!vis[i])
        {
            e=i;
            break;
        }
    return sg[x]=e;
}

注意二者都有相同的跳出循环部分

           if(hash[j]==0)
            {
                sg[i]=j;
                break;
            }

F 对称的几何图形 分成两堆 Nim博弈
G 斐波那契博弈 套模板吧,证明太难了qaq
H sg打表,由于存在分成两堆的情况,要进行拆分重新考虑if(j!=0&&j!=i)vis[sg[j]^sg[i-j]]=true;//拆分也可以dfs实现
递推式求sg函数代码+解答

#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
/* 
int sg[1000];
void init()//sg打表
{
//   memset(sg,0,sizeof(sg));
    sg[0]=0;
    sg[1]=1;
    for(int i=2;i<=1000;i++)
    {
        bool vis[1010]={false};

        for(int j=0;j<=i;j++)
        {
            vis[sg[j]]=true;//取石子
            if(j!=0&&j!=i)vis[sg[j]^sg[i-j]]=true;//拆分
        }
        int j=0;
        while(vis[j]!=0)j++;
        sg[i]=j;
    }
}
int main()
{
//    memset(sg,-1,sizeof(sg));
    init();
    for(int i=1;i<=1000;i++)
    {
        printf("%d\t",sg[i]);
        if(i%4==0)printf("\n");
    }
}
*/
int main()
{
    int T;

    scanf("%d",&T);
    while(T--)
    {
        int n;

        scanf("%d",&n);;
        int temp,ans=0;
        for(int i=0; i<n; i++){
            scanf("%d",&temp);
            int t=temp;
            if(temp%4==3)
            t=temp+1;
            else if(temp%4==0)
            t=temp-1;
            ans^=t;
        }
        if(ans){
            printf("Alice\n");
        }
        else{
            printf("Bob\n");
        }
    }
    return 0;
}

I 看了大神的思路,感觉像是阶梯博弈吧,重点是找到规律(太难了qaq)后将原来的箱子化为两堆,并对奇数项进行Nim博弈操作。
代码

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

int main()
{
    int t,k=0,x,n;
    scanf("%d",&t);
    while(++k<=t)
    {
        int ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&x);
            if(i%6==0||i%6==2||i%6==5)//if(!(i%6==1||i%6==3||i%6==4))
                ans^=x;
        }
        if(ans)
            printf("Case %d: Alice\n",k);
        else
            printf("Case %d: Bob\n",k);
    }
    return 0;
}

K 求第一步的方案个数(异或和的唯一性)。博弈中异或和的极致使用,加深了对XOR的理解。
若a1^a2^…^an!=0,一定存在某个合法的移动,

将ai改变成ai’后满足a1^a2^…^ai’^…^an=0。

若a1^a2^…^an=k,则一定存在某个ai,

它的二进制 表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。

这时ai^k

 js=0;
 for(i=0;i<n;++i)
   {
      t=temp^arr[i];
      if( t<arr[i] )
      ++js;
   }

L 基本同K
M 裸威佐夫博弈(含本题代码)
N 阶梯博弈(逆向思维,从后往前去,注意区间端点的联系)
代码

#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
    int n,f[40];
    while(~scanf("%d",&n))
    {   
        f[1]=9;
        for(int i=2;i<=40;i++)
        f[i]=(i%2)?9*f[i-1]:2*f[i-1];

        for(int i=1;i<=40;i++)
        {
            if(f[i]>=n)
            {
                printf("%s\n",(i%2)?"Stan wins.":"Ollie wins.");
                break;
            }
        }
    }
    return 0;
}

必胜区间:右端点=上一个必败区间的右端点 × 9
必败区间:右端点=上一个必胜区间右端点 × 2

又一天结束了,博弈真好玩

猜你喜欢

转载自blog.csdn.net/qq_40538328/article/details/81434294