博弈论
- 目录
- A Brave Game(HDU1846)
- B kiki's game(HDU2147)
- C Public Sale(HDU2149)
- D 取石子游戏(HDU2516)
- E 取石子游戏(HDU1527)
- F 取(2堆)石子游戏(HDU2177)
- G Being a Good Boy in Spring Festival(HDU1850)
- H Matrix Game(LOJ1247)
- I Incredible Chess(LOJ1186)
- J Left Right(LOJ1192)
- K Partitioning Game(LOJ1199)
- L Paint Chain(HDU3980)
- M Cutting Game(POJ2311)
- N Be the Winner(HDU2509)
- O Crazy Calendar (LOJ1393)
- 总结
写在前面——本文章没有任何关于博弈基础知识的证明部分,只记录当时自己的解题思路
目录
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。
总之这次也是受益匪浅的一次。