Forever Young

从0开始的博弈论

写在前面

菜鸡乱学

被 D 了,不学了

参考资料

Attack佬的博弈总结

扶咕咕的【博弈论】Nim游戏

YYB佬的博弈论总结

巴什博奕

巴什博奕是最简单的一种博弈。

问题模型

两个绝顶聪明的人在玩取石子游戏,一共有 \(n\) 颗石子,游戏规则如下:

  • 两个人轮流取石子.
  • 每次可以取 \(1\sim m\) 颗石子,不能不取.
  • 最先不能取到石子的一方失败.

现在让你判断谁会获胜。

绝顶聪明的意思就是指都会按当前对自己最优的策略取石子,不会出现故意让对手赢的情况。

问题分析

分多种情况考虑:

  1. 只有 \(1\sim m\) 颗石子。

    显然先手必胜,直接取走全部石子即可。

  2. \(m+1\) 颗石子。

    此情况先手必败,假设先手选出 \(x\) 颗石子,那么后手必然会选出剩下的 \(m+1-x\) 颗石子,之后先手无法再选,也就是说无论先手选出多少颗石子,后手都可以把石子取完。

  3. \(m+1\sim 2\times m\) 颗石子。

    此情况先手必胜,先手可以选出若干颗石子,使石子剩下 \(m+1\) 颗,然后后手就遇到了情况2,后手无论取出多少颗,先手都可以把剩下的石子取完。

由上面的分析容易发现当一个人面对 \(m+1\) 颗石子时,他就必败了。

推广至一般的情况:

  1. \((m+1)|n\),即 \(n\)\(m+1\) 的倍数时,先手必败。

    假设有 \(n=k\times(m+1)\) 颗石子。

    先手取出 \(x\) 颗石子,后手就可以取出 \(m+1-x\) 颗石子,石子变为 \((k-1)\times(m+1)\),一直这样到最后先手面对 \((m+1)\) 颗石子,由上面的分析可知这个时候他就必败了。

  2. 否则先手必胜。

    设石子有 \(n=k\times(m+1)+r\) 颗,先手取出 \(r\) 颗石子,就能让后手面对情况1,此时先手必胜。

这样就分析完了全部情况,判断 \(n\) 是否是 \(m+1\) 的倍数即可。

代码实现

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

int n, m;

int main() {
  cin >> n >> m;
  if (n % (m + 1) == 0) puts("houshou");
  else puts("xianshou");
  return 0;
}

例题

HDU1846 Brave Game

裸题,直接判断即可。

HDU 1847 Good Luck in CET-4 Everybody!

类似巴什博奕,但是又不太像,仔细思考吧。

HDU 2188 选拔志愿者

裸题,转化一下即可。

HDU4764 Stone

裸题,转化一下即可。

HDU2149 Public Sale

有点耗脑子了,但是也不难。

Nim游戏

问题模型

两个绝顶聪明的人在玩取石子游戏,一共有 \(n\) 堆石子,规则如下:

  • 两个人轮流取石子.
  • 可以取走一整堆石子,也可以取走一堆石子里的任意个,但是不能跨堆取,不能不取.
  • 最先不能取到石子的一方失败.

判断谁能获胜。

问题分析

解决方法很简单,直接求出每一堆石子数量的异或和,如果异或和为 \(0\) 则先手必败,否则先手必胜。

nim游戏的结束条件很简单,当所有石子的数量都为 \(0\) 时,下一个拿石子的人就失败了,此时的局面是 \(0\bigoplus0\bigoplus0...\bigoplus0=0\)

设每一堆的石子数为 \(a_i,i\in{[1,n]}\)

  • 对先手来说,如果当前局面是 \(a_1\bigoplus a_2\bigoplus a_3...\bigoplus a_n=k\)。那么当前石子中必定存才一个 \(a_i\) 满足 \(a_i\) 的二进制表示里存在 \(k\) 的最高位,否则 \(k\) 的最高位异或和应该是 \(0\)。令这个 \(a_i\bigoplus k\),局面就变成了 \(a_1\bigoplus a_2\bigoplus a_3...\bigoplus a_n=0\),此时先手必胜。
  • 对于先手来说,如果当前局面是 \(a_1\bigoplus a_2\bigoplus a_3...\bigoplus a_n=0\),那么我们不可能将某个 \(a_i\) 异或一个数字之后使得 \(a_1\bigoplus a_2\bigoplus a_3...\bigoplus a_n=0\),此时先手必败。

代码实现

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

int n, a[A];

inline void solve() {
  cin >> n;
  int now = 0;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    now = now ^ a[i];
  }
  if (now) puts("Yes");
  else puts("No");
}

int main() {
  int T = read();
  while (T--) solve();
}

例题

P2197 【模板】nim游戏

板子题。

posted @ 2020-09-10 09:57  Loceaner  阅读(130)  评论(0编辑  收藏  举报