博弈之简单总结


最近看了一下《算法竞赛入门经典——训练指南》中的博弈部分,便急切的做了几道HDU上的博弈水题。

 

大喊一声:我以后会继续更新的(希望如此)。

 

两条规则:

规则1:一个状态是必败状态当且仅当它的所有后继都是必胜状态。

规则2:一个状态时必胜状态当且仅当它至少有一个后继是必败状态。

 

HDU2188 悼念512汶川大地震遇难同胞——选拔志愿者(水)

 

分析:

这种类型的题目叫做巴什博奕(Bash Game),可以直接做.

AC代码如下:

#include<stdio.h>
int main()
{
    int T,n,m;
    while(scanf("%d",&T)!=EOF)
    while(T--)
    {
        scanf("%d%d",&n,&m);
        if(n%(m+1)!=0)
            printf("Grass\n");
        else
            printf("Rabbit\n");
    }
    return 0;
}
View Code

或者按着规则1、规则2推。

AC代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

const int maxn = 10000+10;

bool wining[maxn];

int main(){
    int T, n, m;

    scanf("%d", &T);

    while(T--) {

        scanf("%d%d", &n, &m);

        for(int i=0; i<=m; i++) wining[n-i] = true;
        for(int i=n-m-1; i>=0; i--) {
            wining[i] = false;
            for(int j=1; j<=m; j++) {
                if(i+j <= n && !wining[i+j]) {
                    wining[i] = true;
                    break;
                }
            }
        }

        if(wining[0]) printf("Grass\n");
        else printf("Rabbit\n");
    }

    return 0;
}
View Code

 

HDU2149 Public Sale(水)

分析:

和上一题类似。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

int main() {
    int n, m;

    while(scanf("%d%d", &n, &m) != EOF) {
        if(n % (m+1) == 0) printf("none\n");
        else {
            if(n <= m) {
                for(int i=n; i<=m; i++) {
                    if(i != m) printf("%d ", i);
                    else printf("%d\n", i);
                }
            }
            else {
                int t = n % (m+1);
                printf("%d\n", t);
            }
        }
    }

    return 0;
}
View Code

 

HDU1847 Good Luck in CET-4 Everybody!(水)

分析:

也没啥难度,直接推。

AC代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

const int maxn = 1000 + 10;

int wining[maxn];
bool vis[maxn];

int main() {
    int n;

    while(scanf("%d", &n) == 1) {
        wining[0] = 0;
        for(int i = 1; i <= n; i++) {
            wining[i] = false;
            for(int j = 1; j <= i; j <<= 1) if(!wining[i-j]) {
                wining[i] = true;
                break;
            }
        }
        if(wining[n]) printf("Kiki\n");
        else printf("Cici\n");

    }

    return 0;
}
View Code

或者是SG函数,不过感觉多此一举了,就当练习吧。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

const int maxn = 1000 + 10;

int sg[maxn];
bool vis[maxn];

int main() {
    int n;

    while(scanf("%d", &n) == 1) {
        sg[0] = 0;
        for(int i = 1; i <= n; i++) {
            memset(vis, false, sizeof(vis));
            for(int j = 1; j <= i; j <<= 1) vis[sg[i-j]] = true;
            for(int j = 0; ; j++) if(!vis[j]) {
                sg[i] = j;
                break;
            }
        }

        if(sg[n]) printf("Kiki\n");
        else printf("Cici\n");

    }

    return 0;
}
View Code

 

HDU1849 Rabbit and Grass 

 分析:

本题就是一个 Nim 游戏, 将每个棋子看成一堆火柴, 例如如果 k = 3, 就想象成 一堆火柴含有3根, 每次可以任选一堆中移动不大于 k根。不能移动者输。

利用 Bouton 定理,求 Nim和。

AC代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

int main() {
    int n;
    while(scanf("%d", &n) == 1 && n) {
        int ans = 0, k;
        for(int i = 0; i < n; i++) {
            scanf("%d", &k);
            ans ^= k;
        }

        if(ans == 0) printf("Grass Win!\n");
        else printf("Rabbit Win!\n");
    }


    return 0;
}
    
View Code

 

 HDU1850 Being a Good Boy in Spring Festival 

分析:

Nim 游戏。 利用 Bouton 定理。

难点在于如何求第一步可行的方案数。

设a[1] ^ a[2] ^ a[3] ^ ....^ a[n] = ans (ans != 0)   (1)

那么可行的方案就是选择 1 ~ n 中一堆,减少牌的数量, 使得改变后的 a[1] ^ a[2] ^ a[3] ^ .... ^ a[n] = ans = 0   (2)

假设要改变第 j 堆,设改变后的数量为 a[j]', 使得 a[1] ^ a[2] ^ a[3] ^ ... ^ a[j]' ^ .... ^ a[n] = 0   (3)

设 t = a[1] ^ a[2] ^....^ a[j-1] ^ a[j+2] ^ .... ^ a[n]   (4)

由异或的性质可知 t = ans ^ a[j]   (5)

如何使式子 (3) = 0 呢?   只要使 a[j]' = t 就可以了。因为 ans = a[j]' ^ t = 0.

需要注意的是,只能减少牌的数量,所以 符合要求的 t 要小于 a[j](不能等于哦)

以上是菜鸟的个人见解,并不敢保证思路严谨。莫喷。

AC代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

const int maxn = 100+10;

int a[maxn];

int main() {
    int n;
    while(scanf("%d", &n) == 1 && n) {
        int ans = 0;
        for(int i = 0; i < n; i++) {
            scanf("%d", &a[i]);
            ans ^= a[i];
        }

        if(ans == 0) printf("0\n");
        else {
            int cnt = 0;
            for(int i = 0; i < n; i++) {
                int t = a[i] ^ ans;
                if(t < a[i]) cnt++;
            }
            printf("%d\n", cnt);
        }
    }

    return 0;
}
View Code

 

 

 网上找的博弈的资料:http://www.wutianqi.com/?p=1081

 上面的资料没有对S1,S2,T1,T2等进行定义,个人联系上下文,猜测S1定义为,在S态中充裕堆的维数为1,S2为充裕堆的维数为2. T1,T2同理。

 HDU1907 John (水)

分析:

对应于上面资料中的取火柴游戏中的题目2.

AC代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

int main() {
    int T, n, a;

    scanf("%d", &T);
    while(T--) {
        int ans = 0, cnt = 0;//cnt判断充裕堆的堆数等于0;

        scanf("%d", &n);

        for(int i=0; i<n; i++) {
            scanf("%d", &a);
            ans ^= a;
            if(a > 1) cnt++;
        }

        if(cnt) {
            if(ans) printf("John\n");
            else printf("Brother\n");
        }
        else {
            if(n % 2) printf("Brother\n");
            else printf("John\n");
        }
    }
    return 0;
}
View Code

 

HDU2509 Be the Winner (水)

 分析:

和上一题一样。

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

int main() {
    int n, a;

    while(scanf("%d", &n) == 1) {
        int ans = 0, cnt = 0;//cnt判断充裕堆的堆数等于0;

        for(int i=0; i<n; i++) {
            scanf("%d", &a);
            ans ^= a;
            if(a > 1) cnt++;
        }

        if(ans) {
            if(cnt) printf("Yes\n");
            else printf("No\n");
        }
        else {
            if(cnt) printf("No\n");
            else printf("Yes\n");
        }
    }
    return 0;
}
View Code

 

HDU1944 S-Nim不错的题

详见本博客的一篇题解:http://www.cnblogs.com/tanhehe/archive/2013/05/31/3111181.html

 

HDU1517 A Multiplication Game (不错的题

详见本博客的一篇题解:http://www.cnblogs.com/tanhehe/archive/2013/05/31/3111136.html

 

Simple Game(第二届山东省赛题)

分析:

做的时候是这样想的,本题可以从1~3堆中取,假设对手取2次,那么自己也取2次,再把过程变一下,想象成,对手取一次,自己取一次,对手取一次,自己取一次。取3次的时候也一样。这样就完全是 Nim 博弈了。

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    int T, n, x;

    cin >> T;

    while(T--) {
        cin >> n;
        int ans = 0;
        for(int i=0; i<n; i++) {
            cin >> x;
            ans ^= x;
        }

        if(n <= 3 || ans) cout << "Yes\n";
        else cout << "No\n";
    }
}
View Code

 

posted on 2013-05-28 21:40  Still_Raining  阅读(265)  评论(0编辑  收藏  举报