博弈论学习笔记【未完】

11 篇文章 0 订阅
5 篇文章 0 订阅

博弈论


参考过的文章:

Link 好文要顶

0x00. 初始定义

0x01. 公平游戏的定义

公平游戏的定义:

  1. 两个玩家交替行动。
  2. 其中不能进行移动的玩家就赢了/输了。
  3. 在游戏进行的时候,可以执行的操作和当前是哪一个玩家无关。

0x02. 必胜必败状态的定义

必胜状态的定义:如果这个状态对方无论怎么走都必败,就是一个对于你的必胜状态。

必败状态的定义;如果这个状态对方无论怎么走都必胜,就是一个对于你的必败状态。

0x03. mex运算的定义

mex ⁡ { a 1 , a 2 , a 3 , ⋯   , a k } \operatorname{mex}\{a_1,a_2,a_3,\cdots,a_k\} mex{a1,a2,a3,,ak} 为在 a a a 数组中最小的没有出现过的自然数。自然数是指 ≥ 0 \ge 0 0 的整数。

0x04. SG定理 / SG函数

如果一个状态 x x x 可以转移到 a 1 , a 2 , a 3 , ⋯   , a k a_1,a_2,a_3,\cdots,a_k a1,a2,a3,,ak 这些状态,那这个状态的值就是 mex ⁡ { a 1 , a 2 , a 3 , ⋯   , a k } \operatorname{mex}\{a_1,a_2,a_3,\cdots,a_k\} mex{a1,a2,a3,,ak},其中终止状态的 SG 值为 0 0 0

如果一个状态的 SG 值为 0 0 0 就是必败状态,否则就是必胜状态。

0x05. SJ定理

对于任意一个 Anti-SG 游戏,如果我们规定:当局面中所有的单一游戏的 SG 值为 0 0 0 时,游戏结束,则先手必胜当且仅当满足下列条件:

  1. 游戏的 SG 函数值不为 0 0 0 且游戏中某个单一游戏的 SG 函数值大于 1 1 1
  2. 游戏的 SG 函数值为 0 0 0 且游戏中没有任意一个单一游戏的 SG 函数值大于 1 1 1

0x10. Bash 游戏

人Win LK 和 6872 玩游戏。

1 1 1 堆石子,一共有 n n n 个石子,每一个人一次可以取 1 ∼ k 1\sim k 1k 个石子,问谁可以获胜。

这不是小学奥数题吗

进行分类讨论。

  • 如果 n   m o d   ( k + 1 ) = 0 n\bmod (k+1) = 0 nmod(k+1)=0,那么先手拿 a a a 个,后手拿 k + 1 − a k + 1 - a k+1a 个一定可以拿到最后一个。先手必败。
  • 否则,先手可以先拿 n   m o d   ( k + 1 ) n\bmod (k+1) nmod(k+1) 个,然后后手变成先手,状态变成第一种情况,先手必胜。
bool check(int n, int k)
{ return n % (k + 1); }

0x20. Nim 游戏

0x21. 普通 Nim

Link

人win LK 和 6872 玩游戏。

nim 游戏的规则是这样的:地上有 n n n 堆石子(每堆石子数量小于 1 0 4 10^4 104 ),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如 人Win LK 是先手,且告诉你这 n n n 堆石子的数量,他想知道是否存在先手必胜的策略。

结论:如果每一堆石子的值都异或起来,答案为 0 0 0,那么 6872 必胜,否则 LK 必胜。

证明:

如果轮到某一个人走,却没有办法走,对于他的对手就是必胜状态,那么这种情况所有的石子一定全部都是 0 0 0,也就是每一堆都没有石子,那么异或和也为 0 0 0

否则,如果异或和不为 0 0 0,一定存在某一种方式拿走一些石子让异或和为 0 0 0;同理,如果异或和为 0 0 0,那么一定存在某一种方式拿走一些石子让异或和不为 0 0 0。(不会证明这个鬼玩意)

代码:

#include <bits/stdc++.h>

using namespace std;

signed main()
{
  int T;
  cin >> T;
  while (T --)
  {
    int n, xr = 0;
    cin >> n;
    for (int i = 1; i <= n; i ++)
    {
      int x;
      cin >> x;
      xr ^= x;
    }
    if (xr) cout << "Yes\n";
    else cout << "No\n";
  }
  return 0;
}

AC Link

0x22. 阶梯 Nim

人win LK 和 6872 玩游戏。

有一个 n n n 阶的台阶,每一个台阶都有一些石子,现在人Win LK 和 6872 轮流将石子从上一级台阶移动到下一级台阶,如果没有台阶上有石子就败,人Win LK 想知道他没有必胜策略。

容易发现,如果一个人将偶数级台阶移到了奇数级台阶,那么可以用同样的操作将奇数级台阶移动到偶数级台阶。所以说只要将奇数台阶上的所有石子的异或和异或起来即可。

代码:

#include <bits/stdc++.h>

using namespace std;

signed main()
{
  int n, xr = 0;
  cin >> n;
  for (int i = 1; i <= n; i ++)
  {
    int x;
    cin >> x;
    if (i & 1)
      xr ^= x;
  }
  if (xr) cout << "Yes\n";
  else cout << "No\n";
  return 0;
}

0x30. SG 类问题 / 选取集合类问题

0x31. 一堆石子的选取集合类问题

请看 0xa0。

0x32. 多堆石子的选取集合类问题

人Win LK 和 6872 玩游戏。

n n n 堆石子,每一堆石子只要能拿就可以拿 a 1 , a 2 , a 3 , ⋯   , a k a_1,a_2,a_3,\cdots,a_k a1,a2,a3,,ak 个石子,谁不能拿他的对手就赢了,人Win LK 想要知道他能不能赢。

实际上就是 0x04 SG 定理。使用 SG 定理可以通过这道题。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
int f[N], a[N];
int n, m;

int sg(int x)
{
  if (f[x] != -1)
    return f[x];
  vector <int> arr;
  for (int i = 0; i < m; i ++)
    if (x >= a[i])
      arr.push_back(sg(x - a[i]));
  sort (arr.begin(), arr.end());
  arr.erase(unique(arr.begin(), arr.end()), arr.end());
  set <int> se;
  for (auto &u : arr)
    se.insert(u);
  for (int i = 0; ; i ++)
    if (!se.count(i))
      return f[x] = i;
  return -1;
}

signed main()
{
  memset (f, -1, sizeof f);
  cin >> m;
  for (int i = 0; i < m; i ++)
    cin >> a[i];
  cin >> n;
  int xr = 0;
  for (int i = 0; i < n; i ++)
  {
    int x;
    cin >> x;
    xr ^= sg(x);
  }
  if (xr)
    cout << "Yes\n";
  else
    cout << "No\n";
  return 0;
}

0x40. Anti-SG 类问题

0x41. Anti-Nim 问题

Link

人Win LK 经常和他的好朋友 6872 玩一个非常有趣的游戏:桌子上有 n n n 堆石子,人Win LK 和他的好朋友 6872 轮流取石子,每个人取的时候,可以随意选择一堆石子,在这堆石子中取走任意多的石子,但不能一粒石子也不取,我们规定取到最后一粒石子的人算输。

人Win LK 相当固执,他坚持认为先取的人有很大的优势,所以他总是先取石子,而他的好朋友 6872 就聪明多了,他从来没有在游戏中犯过错误。人Win LK 一怒之前请你来做他的参谋。自然,你应该先写一个程序,预测一下谁将获得游戏的胜利。

结论:先手必胜需要满足所有堆的石子数都为 1 1 1 且异或和为 0 0 0(也就是有偶数堆)或者有一个或者以上堆的石子数 > 1 >1 >1 且石子堆的异或和不为 0 0 0

代码:

#include <bits/stdc++.h>

using namespace std;

int a[55];

signed main()
{
  int T;
  cin >> T;
  while (T --)
  {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++)
      cin >> a[i];
    if (n % 2 == 0)
    {
      bool flag = true;
      for (int i = 1; i <= n; i ++)
        if (a[i] != 1)
        {
          flag = false;
          break;
        }
      if (flag)
      {
        cout << "John\n";
        continue ;
      }
    }
    bool flag = false;
    for (int i = 1; i <= n; i ++)
      if (a[i] > 1)
      {
        flag = true;
        break;
      }
    if (!flag)
    {
      cout << "Brother\n";
      continue ;
    }
    int xr = 0;
    for (int i = 1; i <= n; i ++)
      xr ^= a[i];
    if (xr)
      cout << "John\n";
    else
      cout << "Brother\n";
  }
}

AC Link

0x50. 黄金比博弈 / 威佐夫博弈

0x60. 图 / 树上的博弈论问题

0x61. 有向无环图上的单游戏博弈

题意:给定一个有 N N N 个节点的有向无环图,图中有一个节点有棋子,人Win LK 和 6872 交替移动棋子。

玩家每一步可将这个棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。

对于给定的图和棋子初始位置,人Win LK 和 6872 都会采取最优的行动,人Win LK 想要知道他是否有必胜策略。

实际上就是简单的图上 DP,直接暴力进行状态转移即可。

代码(你觉得这么简单的题还需要代码吗)

0x62. 有向无环图上的多游戏博弈

Link

题意:给定一个有 N N N 个节点的有向无环图,图中某些节点上有棋子,人Win LK 和 6872 交替移动棋子。

玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。

对于给定的图和棋子初始位置,人Win LK 和 6872 都会采取最优的行动,人Win LK 想要知道他是否有必胜策略。

有向无环图(DAG)上的多游戏博弈问题:终点的 sg 函数值为 0 0 0,其他节点的 sg 值为他可以到达的所有点的 sg 值的 mex 值。

然后就可以在图上进行遍历求解 sg 函数的值了。

#include <bits/stdc++.h>

using namespace std;

int n, m, k;
const int N = 2010;
vector <int> z[N];
int f[N];

int sg(int u)
{
  if (~f[u])
    return f[u];
  vector <int> arr;
  for (auto &i : z[u])
    arr.push_back(sg(i));
  sort (arr.begin(), arr.end());
  arr.erase(unique(arr.begin(), arr.end()), arr.end());
  set <int> se;
  for (auto &u : arr)
    se.insert(u);
  for (int i = 0; ; i ++)
    if (!se.count(i))
      return f[u] = i;
  return 114514;
}

int main()
{
  cin >> n >> m >> k;
  while (m --)
  {
    int a, b;
    cin >> a >> b;
    z[a].push_back(b);
  }
  memset (f, -1, sizeof f);
  int xr = 0;
  for (int i = 0; i < k; i ++)
  {
    int x;
    cin >> x;
    xr ^= sg(x);
  }
  if (xr)
    cout << "win\n";
  else
    cout << "lose\n";
  return 0;
}

AC Record

0x70. Every-SG 问题

题意:人Win LK 先走,每个游戏由两堆石子组成,每次可以从较多的那一堆中取走较小那堆的数量的倍数个石子。问人Win LK 赢还是 6872 赢。

如果 x > y x>y x>y 就交换。

如果 ⌊ y x ⌋ = 1 \lfloor\frac{y}{x}\rfloor = 1 xy=1,那么直接计算答案即可。

否则,只需要考虑 k = 1 k=1 k=1 的后续状态即可。

#include <bits/stdc++.h>

using namespace std;

int f[1010][1010], g[1010][1010];

int sg(int x, int y)
{
  if (x > y)
    swap (x, y);
  if (~f[x][y])
    return f[x][y];
  if (x == 0 || y == 0)
    return f[x][y] = 0;
  int a = y % x, b = y / x;
  if (b == 1)
  {
    f[x][y] = sg(a, x) ^ 1;
    g[x][y] = g[a][x] + 1;
    return f[x][y];
  }
  else
  {
    g[x][y] = sg(a, x) + g[a][x] + 1;
    return f[x][y] = 1;
  }
}

signed main()
{
  memset (f, -1, sizeof f);
  int n;
  while (cin >> n)
  {
    int mx = 0, a, b;
    while (n --)
    {
      cin >> a >> b;
      sg(a, b);
      if (a > b)
        swap(a, b);
      mx = max(mx, g[a][b]);
    }
    if (mx & 1)
      cout << "MM\n";
    else
      cout << "GG\n";
  }
}

0x80. Multi-SG 问题

0x81. 分割类博弈

人Win LK 和 6872 正在玩游戏。

人Win LK 先走,每一次拿走 n n n 堆石子中的一堆,假设这一堆石子中有 x x x 个,那么可以放入两堆石子,且放入满足石子的个数小于 x x x 个,不过可以为 0 0 0。如果 x = 0 x=0 x=0 那么不能分割。不能拿的人的对手就赢了,人Win LK 想要知道他有没有必胜策略。

容易发现, x x x 状态可以分裂成 ( a , b ) (a,b) (a,b) 0 ≤ a , b < x 0\le a,b < x 0a,b<x)。

然后引入 SG 定理: s g ( a , b ) = s g ( a ) xor ⁡ s g ( b ) sg(a,b) = sg(a) \operatorname{xor} sg(b) sg(a,b)=sg(a)xorsg(b)。然后只需要枚举 a a a b b b 即可。

代码:

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

const int N = 210;
int f[N], n;

int sg(int x)
{
  if (~f[x])
    return f[x];
  set <int> se;
  for (int i = 0; i < x; i ++)
    for (int j = 0; j <= i; j ++)
      if (!se.count(sg(i) ^ sg(j)))
        se.insert(sg(i) ^ sg(j));
  for (int i = 0; ; i ++)
    if (!se.count(i))
      return f[x] = i;
  return 114514;
}

signed main()
{
  memset (f, -1, sizeof f);
  cin >> n;
  int res = 0;
  for (int i = 1; i <= n; i ++)
  {
    int x;
    cin >> x;
    res ^= sg(x);
  }
  if (res)
    cout << "Yes\n";
  else
    cout << "No\n";
  return 0;
}

0x90. 斐波那契博弈

0xa0. 其他博弈论

0xa1. DP 类博弈论

没有题目,比如说每一次可以拿 2 3 6 数量的棋子,那么直接 DP 转移。

0xa2. 其他类博弈论

请翻我的博客。

0xb0. 总结

我太菜了! \Huge{我太菜了!} 我太菜了!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值