博弈问题方法单-----找规律篇

来源:互联网 发布:ubuntu c 开发 编辑:程序博客网 时间:2024/05/28 19:23

1、题目:

[vijos1196] 吃糖果游戏

题解:

一开始看看感觉还可以用【科学的】二维SG,一看数据,10000位,exm?
于是我先写了个SG函数,敲进去几个数据
啊我好像看到规律了!—> AC

嘛这样打表是不行的,我们还是看个正解吧
当游戏状态属于前者时,Matrix67可以把糖果数被5除余1、4或正好除尽的那一堆分成糖果数被5除余数都是2或3的两堆(他总能做到这一点)
而对方不得不把其中一堆糖果又分出新的糖果数被5除余1、4或正好除尽的一堆留给Matrix67操作。
这样逼着对方总是面临必败的状态,使得最后对方不得不把2个糖果或者3个糖果分成两堆,
从而使Matrix67赢得游戏。
反过来,当Matrix67面临两堆糖果的数目被5除余数都是2或3的状态时,Shadow总可以取胜。
所以就是如果两堆的个数的个位数都是2 3 7 8的一个的话
就是Shadow取胜,不然就是Matrix

代码:

暴力【表】

#include <cstdio>#include <cstring>#include <iostream>using namespace std;int sg[1005][1005],n,m;int get_sg(int x,int y){    if (x>y) swap(x,y);    if (sg[x][y]!=-1) return sg[x][y];    if (x==1 || y==1) {sg[x][y]=1; return 1;}    bool ext[1005];memset(ext,0,sizeof(ext));    for (int i=1;i+i<=y;i++)      ext[get_sg(i,y-i)]=1;    for (int i=1;i+i<=x;i++)      ext[get_sg(i,x-i)]=1;    for (int i=0;;i++)      if (!ext[i]){sg[x][y]=i;break;}    return sg[x][y];}int main(){    memset(sg,-1,sizeof(sg));    while (1)    {        scanf("%d%d",&n,&m);        if (get_sg(n,m)) printf("先手\n");else printf("后手\n");    }}

AC

#include <cstdio>#include <cstring>using namespace std;char x[10005],y[10005];int main(){    for (int i=1;i<=10;i++)    {        scanf("%s%s",x,y);        int l1=strlen(x)-1,l2=strlen(y)-1;        if ((x[l1]=='2'||x[l1]=='3'||x[l1]=='7'||x[l1]=='8')&&(y[l2]=='2'||y[l2]=='3'||y[l2]=='7'||y[l2]=='8'))           printf("Shadow\n");else printf("Matrix67\n");    }}

2、题目

[POJ2505] A multiplication game

题解:

首先想到打表,但这什么鬼啊我不会啊!那就硬找规律?
容易发现
[1,9]Stan
[10,18]Ollie
[19,162]Stan

我们可以这样极限考虑,要是Stan知道ta会赢,那每回合会选最大的9,Ollie知道自己乘上也没什么用,给ta个最小的2,然后Stan选个9就能达到自己的目标,Ollie要是赢的话是同样的
然后你随便试试19-162之间的数就发现Ollie总是不能赢,就可以发现规律了
1~9*1 Stan
9*1+1~9*2 Ollie
9*2+1~9*2*9 Stan
那么我们往下推测
9*2*9+1~9*2*9*2 Ollie
规律get?
其实还有一个问题是这个数字很大,但是可以用double读入

代码:

#include <cstdio>#include <iostream>using namespace std;double n;int main(){    while (~scanf("%lf",&n))    {        while (1)        {            if (n<=9) {printf("Stan wins.\n");break;}            if (n<=18) {printf("Ollie wins.\n");break;}             n/=18;        }    }}

3、题目:

[HDU3032] Nim or not Nim?

题意:

Alice和Bob轮流取石子,每一次可以从任意一堆中拿走任意个石子,也可以将一堆石子分为两个小堆。先拿完者获胜。

题解:

这个问题可以用SG函数来解决。首先,操作①其实和Nim游戏没什么区别,对于一个石子数为k的点来说,后继可以为0…k-1。而操作②实际上是把一个游戏分成了两个游戏。根据游戏的和的概念,这两个游戏的和应该为两个子游戏的SG函数值的异或。
比如说,状态3的后继有:0、1、2、(1,2),他们的SG值分别为0、1、2、3,所以sg(3) = 4
但是一看数据范围2^31 - 1。。。然后打了个表,规律get?

代码:

#include <cstdio>#include <cstring>using namespace std;int sg[10005];int get_sg(int x){    if (sg[x]!=-1) return sg[x];    bool ext[10005];memset(ext,0,sizeof(ext));    for (int i=x-1;i>=0;i--)      ext[get_sg(i)]=1;    for (int i=1;i+i<=x;i++)      ext[get_sg(i)^get_sg(x-i)]=1;    for (int i=0;;i++)      if (!ext[i]) {sg[x]=i; break;}    return sg[x];}int find_sg(int x){    if (x%4==3) return x+1;    if (x%4==0) return x-1;    return x;}int main(){    //表 /*  memset(sg,-1,sizeof(sg));    for (int i=1;i<=50;i++)      printf("sg[%d]=%d\n",i,get_sg(i));*/    int T,n;    scanf("%d",&T);    while (T--)    {        int k=0,a;        scanf("%d",&n);        for (int i=1;i<=n;i++)         {            scanf("%d",&a);            k^=find_sg(a);        }        if (k) printf("Alice\n");else printf("Bob\n");    }}

4、题目:

[BZOJ2463] [中山市选2009] 谁能赢呢?

题解:

这题什么鬼啊,我为什么连暴力都不会啊?
然后画了几个格子试一试,发现早晚他们都会把整个棋盘走一遍,那不就是看这个棋盘格子的奇偶吗。。
再退一步,这不就是看行数的奇偶吗。。
AC。

代码:

#include <cstdio>using namespace std;int main(){    int n;    while (scanf("%d",&n) && n)    {        if (n%2) printf("Bob\n");        else printf("Alice\n");    }}