学习系列 - 博弈论

在蔡大神的论文+讲解和HZW的题库下,自己大概是明白什么是博弈论的皮毛了吧。

 


 

先说SG定理吧。

对于游戏中的状态,我们给每个状态定义一个必胜态和必败态。区别在于前者可以通过一次操作到达必败态,但后者无法做到(后者在一次操作后所能到达的状态全部都为必胜态)

接着引进SG函数,每个状态都有一个SG值,这个值由它所能到达的状态的SG值决定。(这里的所能到达的状态指的是经过一次操作能到达的状态,下同)

SG值有以下性质:

  • SG值始终为非负整数。
  • 每个状态的SG都与它能到达的状态的SG值不相同。
  • 在满足上面两个条件的情况下SG值应该尽量小。

我们会发现,SG=0的状态为必败态,SG≠0的即为必胜态。

SG定理:对于一个组合游戏,它状态的SG值即为各个子游戏状态的SG值的异或和(Nim和)。

反胜利条件:上面的必胜和必败都是基于“无法操作者败”这个规则来解释的。若某组合游戏的胜利条件是“无法操作者胜”的话,必胜态必须满足:

  • SG异或和>0且石子数大于1的堆数>0
  • SG异或和=0且石子数大于1的堆数=0

其余的就是必败态了。

Nim阶梯:不懂。。。反正有这种东西就对了。

Every-SG游戏:每个子游戏必须同时进行的组合游戏。

设D(T),当游戏T为先手必胜时D为最长回合数,当T为先手必败时D为最短回合数。

必胜条件当且仅当Dmax为奇数。

 

常见的几种公平游戏:

Nim游戏:有N堆石子,每次可从一堆石子中拿走任意数量的石子。

解法:把每堆石子看成子游戏,其SG值等于其石子数。

图上移子游戏:一个有向图上有一个棋子沿着有向边移动。

解法:赤裸裸的SG定理直接用。

树上删边:N棵树,不断删边,每次删边也会把删完无法与根相连的子树删去。

解法:把树上的每个点看成一个组合游戏,它的儿子就是子游戏。

图上删边:上一题的升级版,这次图中可以出现环。

解法:先缩环,奇数边环缩成一条边,偶数边环缩成一个点。接着解法同上题。

NimK游戏:Nim的升级版,每次可从K堆拿走任意数量的石子。

解法:把N堆石子的石子数用二进制表示,统计每一个二进制位上的1的个数,若每一位上1的个数mod(k + 1)全为0,则必败,否则必胜。(证明在此

 


 

BZOJ 2281: [SDOI2011]黑白棋

为什么我的第一题都是最丧病的逗QAQ

首先,我们会发现,白棋必须往右移,黑棋必须往左移(最优策略),而且当黑白棋两两紧靠时就是必败态。

这样我们可以把第i个白棋和第i个黑棋之间的空格看成第i个石子堆,问题即可转化成NimK问题。

然后用组合数学算出必败态的方案数,再用总方案数减去必败方案数即可得到必胜方案数。

博弈论+组合数学+DP。。。

我要吐了。。。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define ll long long
#define maxn 10009
#define MAX 1<<30
#define Q 1000000007

using namespace std;

int n, k, d, bin[16];
ll dp[maxn], c[maxn][105], ans;

void Add(ll &a, ll b) { a=(a+b)%Q; }

int C(int a, int b)
{
    if (b>a-b) b = a-b;
    return c[a][b];
}

int main()
{
    
    scanf("%d%d%d", &n, &k, &d); k/=2;
    rep(i, 0, n) c[i][0] = 1;
    rep(i, 0, n) rep(j, 1, min(i, k*2)) c[i][j] = (c[i-1][j-1]+c[i-1][j])%Q;
    dp[0] = 1; bin[1] = 1; rep(i, 2, 15) bin[i] = bin[i-1]*2;
    rep(i, 0, 14) down(j, n-k*2, 0)
    {
        int q = 1;
        while (q*(d+1)*bin[i+1]+j <= n-k*2 && q*(d+1) <= k) 
            { Add(dp[q*(d+1)*bin[i+1]+j], dp[j]*C(k,q*(d+1))); q++; }
    }
    rep(i, 0, n-k*2) Add(ans, dp[i]*C(n-i-k,k));
    ans = (C(n,k*2)%Q+Q-ans)%Q;
    printf("%lld", ans);
    return 0;
}
View Code

BZOJ 2281: Nim

第一眼看到名字,以为真的是赤裸裸的Nim问题,结果还是被骗了。

尼玛这不就是一道数据结构题吗,维护一棵树支持点修改和链查询。。。

逗比的我用了树链剖分,硬是把DFS序的功能忘得一干二净QAQ

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define ll long long
#define maxn 10009
#define MAX 1<<30
#define Q 1000000007

using namespace std;

int n, k, d, bin[16];
ll dp[maxn], c[maxn][105], ans;

void Add(ll &a, ll b) { a=(a+b)%Q; }

int C(int a, int b)
{
    if (b>a-b) b = a-b;
    return c[a][b];
}

int main()
{
    
    scanf("%d%d%d", &n, &k, &d); k/=2;
    rep(i, 0, n) c[i][0] = 1;
    rep(i, 0, n) rep(j, 1, min(i, k*2)) c[i][j] = (c[i-1][j-1]+c[i-1][j])%Q;
    dp[0] = 1; bin[1] = 1; rep(i, 2, 15) bin[i] = bin[i-1]*2;
    rep(i, 0, 14) down(j, n-k*2, 0)
    {
        int q = 1;
        while (q*(d+1)*bin[i+1]+j <= n-k*2 && q*(d+1) <= k) 
            { Add(dp[q*(d+1)*bin[i+1]+j], dp[j]*C(k,q*(d+1))); q++; }
    }
    rep(i, 0, n-k*2) Add(ans, dp[i]*C(n-i-k,k));
    ans = (C(n,k*2)%Q+Q-ans)%Q;
    printf("%lld", ans);
    return 0;
}
View Code

BZOJ 2463: [中山市选2009]谁能赢呢?

判断奇偶即可。

证明:用多米诺铺满棋盘,然后自行观察。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define ll long long
#define maxn 10009
#define MAX 1<<30
#define Q 1000000007

using namespace std;

int n, k, d, bin[16];
ll dp[maxn], c[maxn][105], ans;

void Add(ll &a, ll b) { a=(a+b)%Q; }

int C(int a, int b)
{
    if (b>a-b) b = a-b;
    return c[a][b];
}

int main()
{
    
    scanf("%d%d%d", &n, &k, &d); k/=2;
    rep(i, 0, n) c[i][0] = 1;
    rep(i, 0, n) rep(j, 1, min(i, k*2)) c[i][j] = (c[i-1][j-1]+c[i-1][j])%Q;
    dp[0] = 1; bin[1] = 1; rep(i, 2, 15) bin[i] = bin[i-1]*2;
    rep(i, 0, 14) down(j, n-k*2, 0)
    {
        int q = 1;
        while (q*(d+1)*bin[i+1]+j <= n-k*2 && q*(d+1) <= k) 
            { Add(dp[q*(d+1)*bin[i+1]+j], dp[j]*C(k,q*(d+1))); q++; }
    }
    rep(i, 0, n-k*2) Add(ans, dp[i]*C(n-i-k,k));
    ans = (C(n,k*2)%Q+Q-ans)%Q;
    printf("%lld", ans);
    return 0;
}
View Code

POJ 3537: Crosses and Crosses

初始时为状态N,假设我们在第K格中打叉,那么状态N会分裂成状态K-3和状态N-K-2。SG定理搞。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 2345
#define MAX 1<<30

using namespace std;

int n, sg[maxn];

int Win(int n)
{
    if (n<0) return 0;
    if (sg[n]>=0) return sg[n]; else sg[n] = 0;
    bool b[maxn];
    rep(i, 0, maxn) b[i] = 0;
    rep(i, 1, n) 
        b[Win(i-3) ^ Win(n-i-2)] = 1;
    while (b[sg[n]]) sg[n]++;
    return sg[n];
}

int main()
{
    scanf("%d", &n);
    rep(i, 0, n) sg[i] = -1;
    if (Win(n)) printf("1\n"); else printf("2\n");
    return 0;
}
View Code

BZOJ 1188: [HNOI2007]分裂游戏

子游戏即每颗豆子。状态分裂+SG定理。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 2345
#define MAX 1<<30

using namespace std;

int t, n, sg[maxn], q[maxn], a, ans;

int Cal(int x)
{
    if (sg[x]>=0) return sg[x]; else sg[x] = 0;
    bool b[99];
    rep(i, 0, 99) b[i] = false;
    rep(i, 1, x-1) rep(j, 1, x-1) b[Cal(i) ^ Cal(j)] = true;
    while (b[sg[x]]) sg[x]++;
    return sg[x];
}

int main()
{
    rep(i, 1, 21) sg[i] = -1;
    t = Cal(21);
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        down(i, n, 1) scanf("%d", &q[i]);
        a = ans = 0; rep(i, 1, n) if (q[i]%2) a ^= sg[i];
        if (!a) { printf("-1 -1 -1\n0\n"); continue; }
        down(i, n, 2) if (q[i]) down(j, i-1, 1) down(k, j, 1) 
            if ((sg[i]^sg[j]^sg[k]) == a) 
                { if (!ans) printf("%d %d %d\n", n-i, n-j, n-k); ans++; }
        printf("%d\n", ans);
    }
    return 0;
}
View Code

POJ 3710: Christmas Game

图上删边游戏。

但这道题的图比普通图多了一个定义:每个环上只有一个度数大于2的点。所以我们可以利用这个特性来缩环。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 1234
#define MAX 1<<30

using namespace std;

struct edge {int y, n;} e[maxn]; 
int t, n, m, q, x, y, ans, fir[maxn], sg[maxn], h[maxn], b[maxn];

int Cal(int l, int x)
{
    int o = fir[x], y = e[o].y;
    b[x] = l;
    while (o)
    {
        if (y != h[x] && b[y] && b[y] < l) return sg[x] = (l-b[y])%2 ? b[y]-l : b[y]-l+1;
        if (!b[y] || b[y] == l+1) { h[y] = x; sg[x] ^= Cal(l+1, y)+1; }
        o = e[o].n, y = e[o].y;
    }
    return sg[x];
}

int main()
{
    while (scanf("%d", &t) != EOF)
    {
        ans = 0;
        while (t--)
        {
            scanf("%d %d", &m, &n); 
            rep(i, 1, m) sg[i] = fir[i] = h[i] = b[i] = 0;
            m = 0; 
            rep(i, 1, n) 
            {
                scanf("%d %d", &x, &y);
                e[++m].y = y; e[m].n = fir[x]; fir[x] = m;
                e[++m].y = x; e[m].n = fir[y]; fir[y] = m;
            }
            ans ^= Cal(1, 1);
        }
        if (ans) printf("Sally\n"); else printf("Harry\n");
    }
    return 0;
}
View Code

POJ 1704: Georgia and Bob

Nim阶梯的应用,两两分组转成Nim游戏。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 1234
#define MAX 1<<30

using namespace std;

int t, n, a, k[maxn];

int main()
{
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n); a = 0;
        rep(i, 1, n) scanf("%d", &k[i]);
        sort(k+1, k+1+n);
        for (int i=n; i>0; i-=2) a ^= k[i]-k[i-1]-1;
        if (a) printf("Georgia will win\n"); else printf("Bob will win\n");
    }
    return 0;
}
View Code

POJ 3480: John

Nim阶梯的应用,两两分组转成Nim游戏。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 123
#define MAX 1<<30

using namespace std;

int t, n, ans, a[maxn], b;

int main()
{
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n); ans = b = 0;
        rep(i, 1, n) scanf("%d", &a[i]);
        rep(i, 1, n) ans ^= a[i];
        rep(i, 1, n) if (a[i]>1) b++;
        if (!ans && !b) printf("John\n");
        else if (ans && b) printf("John\n");
        else printf("Brother\n");
    }
    return 0;
}
View Code

POJ 2068: Nim

Nim的变种。SG定理搞。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 12345
#define MAX 1<<30

using namespace std;

int n, s, q[23], b[23][maxn];

int Find(int x, int l)
{
    if (b[x][l]) return b[x][l];
    rep(i, 1, min(l, q[x])) if (Find(x+1>n*2?1:x+1, l-i) == 1) return b[x][l] = 2;
    return b[x][l] = 1;
}

int main()
{
    scanf("%d", &n);
    while (n)
    {
        scanf("%d", &s);
        rep(i, 1, n*2) rep(j, 1, s) b[i][j] = 0;
        rep(i, 1, n*2) b[i][0] = 2;
        rep(i, 1, n*2) scanf("%d", &q[i]);
        printf("%d\n", Find(1, s)-1);
        scanf("%d", &n);
    }
    return 0;
}
View Code

POJ 2960: S-Nim

Nim的变种。依旧SG定理搞。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 12345
#define MAX 1<<30

using namespace std;

int t, n, m, s[123], a, sg[12345], ans;

int main()
{
    scanf("%d", &m);
    while (m)
    {
        rep(i, 1, m) scanf("%d", &s[i]);
        rep(x, 0, 10000) 
        {
            bool b[123]; sg[x] = 0;
            rep(i, 0, 100) b[i] = false; 
            rep(i, 1, m) if (s[i]<=x) b[sg[x-s[i]]] = true;
            while (b[sg[x]]) sg[x]++;
        }
        scanf("%d", &t); 
        rep(i, 1, t)
        {
            scanf("%d", &n); ans = 0;
            rep(i, 1, n) { scanf("%d", &a); ans ^= sg[a]; }
            if (ans) printf("W"); else printf("L");
        }
        printf("\n"); scanf("%d", &m);
    }
    return 0;
}
View Code

POJ 2484: A Funny Game

先考虑链的SG值,再求初始状态的SG值。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 12345
#define MAX 1<<30

using namespace std;

int t, n;

int main()
{
    scanf("%d", &n);
    while (n)
    {
        if (n < 3) printf("Alice\n"); else printf("Bob\n");
        scanf("%d", &n);
    }
    return 0;
}
View Code

POJ 2505: A multiplication game

2-9:必胜

10-18:必败

19-162:必胜

163-324:必败

……

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 12345
#define MAX 1<<30

using namespace std;

long long n, a;

int main()
{
    while (scanf("%lld", &n) != EOF)
    {
        a = 9;
        while (n > a*2) a *= 18;
        if (n <= a) printf("Stan wins.\n"); else printf("Ollie wins.\n");
    }
    return 0;
}
View Code

POJ 2425: A Chess Game

图上移子游戏。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 1234
#define MAX 1<<30

using namespace std;

struct e{int y, n;} e[1234567]; int f[maxn];
int n, m, k, a, ans, sg[maxn];

int Cal(int x)
{
    if (sg[x]>=0) return sg[x]; else sg[x] = 0;
    bool b[maxn]; rep(i, 0, n) b[i] = false;
    int o = f[x];
    while (o) 
    {
        b[Cal(e[o].y)] = true;
        o = e[o].n;
    }
    while (b[sg[x]]) sg[x]++;
    return sg[x];
}

int main()
{
    while (scanf("%d", &n) != EOF)
    {
        rep(i, 0, n-1) f[i] = 0, sg[i] = -1;
        rep(x, 0, n-1) 
        {
            int y;
            scanf("%d", &k);
            rep(i, 1, k)
            {
                scanf("%d", &y);
                e[++m].y = y, e[m].n = f[x], f[x] = m;
            }
        }
        rep(i, 0, n-1) if (sg[i] < 0) sg[i] = Cal(i);
        scanf("%d", &k);
        while (k)
        {
            ans = 0;
            rep(i, 1, k) { scanf("%d", &a); ans^=sg[a]; }
            if (ans) printf("WIN\n"); else printf("LOSE\n");
            scanf("%d", &k);
        }
    }
    return 0;
}
View Code

POJ 2975: Nim

普通Nim。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 1234
#define MAX 1<<30

using namespace std;

int n, a[maxn], ans, m;

int main()
{
    scanf("%d", &n);
    while (n)
    {
        rep(i, 1, n) scanf("%d", &a[i]);
        ans = m = 0; 
        rep(i, 1, n) ans ^= a[i];
        rep(i, 1, n) if (a[i] >= (a[i]^ans)) m++;
        if (!ans) m = 0;
        printf("%d\n", m); 
        scanf("%d", &n);
    }
    return 0;
}
View Code

POJ 1740: A New Stone Game

Nim+移子游戏。

游戏状态的必胜必败性可分成奇数堆和偶数堆来看。

偶数堆的话若能两两分组且每组的石子数都相等的话则为必败态(想想全部堆为空的情况)其余的为必胜态。

奇数堆全部都是必胜态。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 123
#define MAX 1<<30

using namespace std;

int n, a[maxn], b[maxn];
bool e;

int main()
{
    scanf("%d", &n);
    while (n)
    {
        rep(i, 0, 100) b[i] = 0; 
        rep(i, 1, n) scanf("%d", &a[i]);
        if (n%2) printf("1\n"); else 
        {
            rep(i, 1, n) b[a[i]]++;
            e = false;
            rep(i, 0, 100) if (b[i]%2) e = true;
            if (e) printf("1\n"); else printf("0\n");
        }
        scanf("%d", &n);
    }
    return 0;
}
View Code

POJ 2234: Matches Game

普通Nim。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 123
#define MAX 1<<30

using namespace std;

int n, a, ans, b;
bool e;

int main()
{
    while (scanf("%d", &n) != EOF)
    {
        ans = b = 0;
        rep(i, 1, n) 
        {
            scanf("%d", &a);
            ans ^= a;
            if (a>1) b++;
        }
        if ((ans && b) || (!ans && !b)) printf("Yes\n"); else printf("No\n");
    }
    return 0;
}
View Code

CF 493D: Matches Game

判断棋盘奇偶性即可。

证明:若是奇数则必败,因为对手可以模仿你的动作(沿中轴对称模仿)。若偶数时向右移一步则变成对手的必败态了。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 100009
#define MAX 1<<30

using namespace std;

int n, m;

int main()
{
    scanf("%d", &n);
    if (n&1) printf("black\n"); else printf("white\n1 2\n");
    return 0;
}
View Code

CF 451A: Matches Game

一眼题,看行列的最小值的奇偶性。

#include <cstdlib>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>

#define rep(i, l, r) for(int i = l; i <= r; i++)
#define down(i, l, r) for(int i = l; i >= r; i--)
#define maxn 100009
#define MAX 1<<30

using namespace std;

int n, m;

int main()
{
    scanf("%d%d", &n, &m);
    if (min(n, m)&1) printf("Akshat"); else printf("Malvika");
    return 0;
}
View Code
 

 
共18道题。。。
posted @ 2015-02-01 22:16  NanoApe  阅读(534)  评论(0编辑  收藏  举报
AmazingCounters.com