暑假集训专题练习记录———博弈论


写在前面——本文章没有任何关于博弈基础知识的证明部分,只记录当时自己的解题思路

目录

A Brave Game(HDU1846)

思路:
简单巴什博弈。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        int n, m;
        cin >> n >> m;
        if (n % (m + 1) == 0)
            cout << "second" << endl;
        else
            cout << "first" << endl;
    }

    return 0;
}

B kiki’s game(HDU2147)

题目大意:
移动硬币从右上(1,m)移动到左下(n,1)。移动规则是,每次只能向下或者向左或者左下移动一格。
思路:
画图找规律(?)如下
在这里插入图片描述可以发现,如果行数或者列数是奇数的时候,先手必败,反之必胜。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n, m;
    while (cin >> n >> m)
    {
        if (n == 0 && m == 0)
            break;
        if (n % 2 == 0 || m % 2 == 0)
            cout << "Wonderful!" << endl;
        else
            cout << "What a pity!" << endl;
    }

    return 0;
}

C Public Sale(HDU2149)

思路:
简单巴什博弈。
需要注意的是,如果成本小于我每次加价的最大值,那么可能有多个值,循环输出就行了。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n, m;
    while (cin >> n >> m)
    {
        if (n <= m)
        {
            for (int i = n; i <= m; i++)
            {
                cout << i;
                if (i != m)
                    cout << " ";
            }
            cout << endl;
        }
        else if (n % (m + 1) == 0)
            cout << "none" << endl;
        else
            cout << n % (m + 1) << endl;
    }

    return 0;
}

D 取石子游戏(HDU2516)

思路:
斐波那契博弈。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
int f[50];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    f[0] = 2, f[1] = 3;
    for (int i = 2; i <= 50; i++)
        f[i] = f[i - 1] + f[i - 2];

    int n;
    while (cin >> n)
    {
        if (n == 0)
            break;
        bool isok = false;
        for (int i = 0; i <= 50; i++)
        {
            if (f[i] == n)
            {
                isok = true;
                cout << "Second win" << endl;
            }
        }
        if (!isok)
            cout << "First win" << endl;
    }

    return 0;
}

E 取石子游戏(HDU1527)

思路:
威佐夫博弈(就是那个黄金分割比那个)
AC代码

#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int a, b;
    while (cin >> a >> b)
    {
        if (a > b)
            swap(a, b);
        if (a == (int)((b - a) * ((sqrt(5.0) + 1) / 2)))
            cout << "0" << endl;
        else
            cout << "1" << endl;
    }

    return 0;
}

F 取(2堆)石子游戏(HDU2177)

思路:
还是威佐夫博弈,但是这个问了第一步的所有解法。就枚举判断一下就可以了。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
int get(int da, int db)
{
    return (int)((db - da) * ((sqrt(5) + 1)) / 2);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int a, b;
    while (cin >> a >> b)
    {
        if (a == 0 && b == 0)
            break;
        int c = (int)((b - a) * ((sqrt(5) + 1)) / 2);
        if (a == c)
            cout << "0" << endl;
        else
        {
            cout << "1" << endl;

            for (int i = 1; i <= a; i++) //两堆同时取i个
            {
                int da = a - i, db = b - i;
                if (da == get(da, db))
                {
                    cout << da << " " << db << endl;
                    break;
                }
            }

            int ma = 0, mb = 0;
            for (int i = 1; i < a; i++) //a堆取i个
            {
                int da = a - i;
                if (da == get(da, b))
                {
                    ma = da, mb = b;
                    cout << da << " " << b << endl;
                    break;
                }
            }

            for (int i = 1; i < b; i++) //b堆取i个
            {
                int db = b - i, da = a;
                if (db < da)
                    swap(da, db);
                if (da == get(da, db) && da != ma && db != mb)
                {
                    cout << da << " " << db << endl;
                    break;
                }
            }
        }
    }

    return 0;
}

G Being a Good Boy in Spring Festival(HDU1850)

思路:
Nim博弈。
借用一个大佬博客的话,如果想要更改其中一个值使得异或结果为0的话,需要原值大于剩余的异或和时才成立。
具体的不是很明白了

AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int arr[max_n];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n;
    while (cin >> n)
    {
        if (n == 0)
            break;
        int sum = 0;
        for (int i = 0; i < n; i++)
        {
            cin >> arr[i];
            sum ^= arr[i];
        }

        if (sum == 0)
            cout << "0" << endl;
        else
        {
            int cnt = 0;
            for (int i = 0; i < n; i++)
            {
                if ((sum ^ arr[i]) < arr[i])
                    cnt++;
            }
            cout << cnt << endl;
        }
    }

    return 0;
}

H Matrix Game(LOJ1247)

题目大意:
从n*m的矩阵中选一行,拿任意数量的列和任意的石头,问最后谁赢。
思路:
其实就是你可以拿一行任意是数量的石头,那么对每行求和,就变成Nim了。
AC代码

//LOJ 1247
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int t = 1; t <= T; t++)
    {
        int n, m;
        cin >> n >> m;
        int cnt = 0;
        for (int i = 0; i < n; i++)
        {
            int ans = 0;
            for (int j = 0; j < m; j++)
            {
                int tmp;
                cin >> tmp;
                ans += tmp;
            }
            cnt ^= ans;
        }

        if (cnt== 0)
            cout << "Case " << t << ": Bob" << endl;
        else
            cout << "Case " << t << ": Alice" << endl;
    }

    return 0;
}

I Incredible Chess(LOJ1186)

题目大意:
n*n的棋盘,黑棋子在上面,白棋子在下面。棋子可以向前或者向后任意步,不能越过,不能动算输。
思路:
首先,后退是不行的,不会改变先后手关系,所以只需要看两个棋子之间的距离就行了。Nim。
AC代码

//LOJ 1186
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e2 + 100;

int arr[max_n];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int k = 1; k <= T; k++)
    {
        memset(arr, -1, sizeof arr);
        int n;
        cin >> n;
        int ans = 0;
        //0表示黑色 1表示白色
        for (int i = 0; i < n; i++)
            cin >> arr[i];

        for (int i = 0; i < n; i++)
        {
            int tmp;
            cin >> tmp;
            arr[i] = tmp - arr[i] - 1;
            ans ^= arr[i];
        }

        if (ans == 0)
            cout << "Case " << k << ": black wins" << endl;
        else
            cout << "Case " << k << ": white wins" << endl;
    }

    return 0;
}

J Left Right(LOJ1192)

题目大意:
棋子可以往左移也可以往右移,不能超过棋子,不能动者输。
思路:
题目保证了棋子数量是偶数,所以并不用讨论棋子是否是奇数的情况了。
既然是偶数,那么就把两两棋子看成一对,然后他们中间的距离Nim。
AC代码

//LOJ 1192
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e2 + 100;
int arr[max_n];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int k = 1; k <= T; k++)
    {
        int n;
        cin >> n;
        for (int i = 0; i < n * 2; i++)
            cin >> arr[i];

        int ans = 0;
        for (int i = 1; i < 2 * n; i += 2)
            ans ^= (arr[i] - arr[i - 1] - 1);

        if (ans == 0)
            cout << "Case " << k << ": Bob" << endl;
        else
            cout << "Case " << k << ": Alice" << endl;
    }

    return 0;
}

K Partitioning Game(LOJ1199)

题目大意:
刚开始有n堆石子,可以选择一堆石子,将其分为不相等的两堆,不能分的人输。
思路:
sg。
枚举分的情况,一堆是j,另一堆就是i-j。
AC代码

//LOJ 1199
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e4 + 100;
int f[max_n];
int cnt[max_n];

void sg()
{
    for (int i = 1; i <= max_n - 100; i++)
    {
        memset(cnt, 0, sizeof cnt);
        
        for (int j = 1; j + j < i; j++)
              cnt[f[j] ^ f[i - j]] = 1;
        for (int j = 0; j <= max_n - 100; j++)
        {
            if (!cnt[j])
            {
                f[i] = j;
                break;
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    sg();

    int T;
    cin >> T;

    for (int k = 1; k <= T; k++)
    {
        int n;
        cin >> n;
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
            int tmp;
            cin >> tmp;
            ans ^= f[tmp];
        }
        if (ans == 0)
            cout << "Case " << k << ": Bob" << endl;
        else
            cout << "Case " << k << ": Alice" << endl;
    }

    return 0;
}

L Paint Chain(HDU3980)

题目大意:
有n个石子围成圈,每次能拿m个,不能拿的输。
思路:
首先先让第一个人拿m个,这样就变成了一条链了,并且交换了先后手。
然后sg枚举拿的情况就行了,详见注释(注释也只有一行啊喂
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e3 + 100;

int f[max_n][max_n];
int sg(int n, int m)
{
    if (f[n][m] != -1)
        return f[n][m];
    if (n < m)
        return f[n][m] = 0;

    set<int> s;
    for (int i = m; i <= n; i++) //分成两条链 1条从1到i 一条从i+m+1到n
        s.insert(sg(i - m, m) ^ sg(n - i, m));

    for (int i = 0;; i++)
        if (!s.count(i))
            return f[n][m] = i;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    memset(f, -1, sizeof f);

    int T;
    cin >> T;
    for (int k = 1; k <= T; k++)
    {
        int n, m;
        cin >> n >> m;
        if (n < m || sg(n - m, m))
            cout << "Case #" << k << ": abcdxyzk" << endl;
        else
            cout << "Case #" << k << ": aekdycoin" << endl;
    }
    return 0;
}

M Cutting Game(POJ2311)

题目大意:
有一张w*h的纸,每次只能横着或者竖着剪,最后不能剪得人输。
思路:
sg枚举所有可能要剪得行和列。
AC代码

#include <iostream>
#include <set>
#include <cstring>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 2e2 + 100;

int f[max_n][max_n];
int sg(int w, int h)
{
    if (f[w][h] != -1)
        return f[w][h];

    set<int> s;
    for (int i = 2; w - i >= 2; i++) //剪i行
        s.insert(sg(i, h) ^ sg(w - i, h));
    for (int i = 2; h - i >= 2; i++) //减i列
        s.insert(sg(w, i) ^ sg(w, h - i));

    for (int i = 0;; i++)
        if (!s.count(i))
            return f[w][h] = i;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    memset(f, -1, sizeof f);

    int w, h;
    while (cin >> w >> h)
    {
        if (sg(w, h))
            cout << "WIN" << endl;
        else
            cout << "LOSE" << endl;
    }
    return 0;
}

N Be the Winner(HDU2509)

题目大意:
m个石子n组,每次拿任意数量连续的石子,不能拿的赢
思路:
反Nim博弈。
结论:
先手必胜条件
1、XOR = = 0 && SUM = = N(sum==n就是说每个数都是1)
2、 XOR != 0 && SUM > N(sum>n就是说,至少有一个数大于1)
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n;
    while (cin >> n)
    {
        int ans = 0;
        int sum = 0;
        for (int i = 0; i < n; i++)
        {
            int tmp;
            cin >> tmp;
            sum += tmp;
            ans ^= tmp;
        }
        //先手必胜条件 XOR == 0 && SUM == N || XOR != 0 &&SUM > N
        if ((sum > n && ans) || (!ans && sum == n))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }

    return 0;
}

O Crazy Calendar (LOJ1393)

题目大意:
有一个r*c的矩阵,里面放着硬币,每次选择一个格子把其中的硬币放到正下或者正右方,不能放的输。
思路:
关键的思路写到代码注释了,这里需要说明的是,注释写的距离指的是曼哈顿距离。
(另外想吐槽一下自己刚开始还想着动态开数组去存数据,,,
AC代码

//LOJ 1393
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        int r, c;
        cin >> r >> c;
        //目标位置 r c
        //如果当前与目标的距离是偶数,不影响先后手顺序 先手必胜
        //如果当前与目标距离是奇数,那么动一步就变成了偶数
        //综上可以明白这个游戏 只有奇数会影响先后手,那么单看奇数的话,就是nim游戏(选择一堆,取走任意数量
        int ans = 0;
        for (int i = 0; i < r; i++)
        {
            for (int j = 0; j < c; j++)
            {
                int x;
                cin >> x;
                if ((r - i + c - j) % 2 != 0)
                    ans ^= x;
            }
        }
        if (ans == 0)
            cout << "Case " << i << ": lose" << endl;
        else
            cout << "Case " << i << ": win" << endl;
    }

    return 0;
}

总结

博弈论部分感觉特别考验一个人的智商。sg那部分,目前来看是会用了,nim博弈掌握的也不错。这次做题的时候,看到自己去HDU上交了那么多发,就会在想自己到底在跟谁博弈,或许是判题姬qwq。
总之这次也是受益匪浅的一次。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值