博弈论(附带 二进制)
补:二进制
转换
二进制转十进制
:数字x基数^位次幂+
例如:二进制数0101用十进制表示的数就是:1x2^0 + 0x2 ^1 + 1x2 ^2 + 0x2 ^3 = 5
十进制转二进制1.(短除法)
:除2取余(从下往上取)
十进制转二进制2.
:与二进制转十进制相反
七种位运算
& :按位与(AND)(∪)
a&b 把ab转化为二进制,对于每一位,如果ab这一位都是1,这该位是1,否者为0.将所有位的结果累加
- 遇0则0。如:1 & 1=1,1 & 0=0。只要有0结果就是0。
| :按位或(OR)(∩)
a | b 把ab转化为二进制,对于每一位,如果ab至少有一个是1,则该位结果是1,否者为0.将所有位的结果累加
- 遇1则1。如:1 | 1=1,1 | 0=1。只要有1结果就是1。
~:取反(NOT)(¬)
,1-0 0-1
^ :按位异或(XOR)(⊕)
a ^ b 把a b转化为二进制,对于每一位,如果a b恰好有一个是1,则该位结果是1,否者为0.然后将结果转化为十进制
- 口诀:不进位加(相同为0,相异为1)。如:1 ^ 0=1,1 ^ 1=0。
" >> ":右移
a>>b 把a转化为二进制,把所有数位向右移动b个位置,相当于a / 2的b次幂
" << ":左移
a<<b 把a转化为二进制,把所有数位向左移动b个位置,新多的位置补零 ,相当于a * 2的b次幂
" >>> ":无符号右移
。不管符号位是0还是1,都是补0。
Nim取子问题简单证明
转: 链接.
基础知识
- 通常会将先手必败的局面称为奇异局势。
- 减去某一个数,等价于将被减数当中若干个0变成1,1变成0。
- 可以证明:任何一个偶状态在其中一个数变小后
一定成为
奇状态,而一个奇状态一定可以
通过改变一个数变成偶状态.
先是3堆石子
关键:
- 我们有3个数,如果这三个数的每一位的1的数量和都是偶数,也就是数量和不是0就是2的话,那么这一定是一个奇异局面。
举个例子,比如[10, 8, 2]是一个奇异局面,我们把它们写成二进制。10的二进制是1010,8的二进制是1000,2的二进制是10。所以我们可以发现这三个数的二进制位加起来,第1、2、3位都出现了两个1。这个时候先手不论如何操作,后手只需要保证剩下的三个数的二进制位维持这个特性即可。这样做可以保证最后一次拿取结束之后,给先手留下[0, 0, 0]的局面。本质上来说,它的原理和两堆石子的时候是一样的,只不过转化了一种形式。
举个例子,比如我们从10当中拿走3颗石子,得到(7, 8, 2),我们观察二进制位分别是111, 1000, 10。会发现每一位1的数量从低到高分别是[1, 2, 1, 1]。所以我们可以从1000拿取3个石子,保证留下的数量是101,也就是5。这样剩下的1的个数就是[2, 2, 2],依然是偶数。所以先手不论如何拿,后手都可以保证一定可以让留下的数字在二进制上保持偶数,先手一定必败。在不满足这个条件的局面当中先手一定必胜,因为先手可以在第一次通过拿取掉多余的1,保证留下一个必败的局面给后手。
这也是这题的解法,即通过二进制位来判断是否先手必胜。
- 判断每个二进制位当中出现的1的次数和是否是偶数,可以通过位运算的
亦或
来完成。(在亦或操作当中,对每一个二进制位进行计算,奇数为1,偶数为0。所以我们只需要计算一下这三堆石子亦或之后的结果是否为0,就可以知道是否每一个二进制位的1的数量是否都是偶数了。)
def win_or_lose(a, b, c):
return (a ^ b ^ c) == 0
推广 Bouton定理
定理的内容是
先手可以在非平衡的Nim博弈中取胜,
而后手可以在平衡的Nim博弈中取胜。
(这里的 平衡
就是指的是所有二进制位1的数量是偶数。)
def win_or_lose(nums):
ret = 0
for i in nums:
ret ^= i
return ret == 0
下面转自链接: link.
巴什博弈(Bash Game)
只有一堆n个物品,两个人从轮流中取出(1~m)个;最后取光者胜。
考虑到 若n=m+1
那么 第一个人不论如何取都不能取胜。
进一步我们发现 若 n=k*(m+1)+r; 先取者拿走 r 个,那么后者再拿(1~m)个
n=(k-1)*(m+1)+s; 先取者再拿走s 个 最后总能造成 剩下n=m+1 的局面。
因此,此时先手有必赢策略。
相对应的,若n=k*(m+1) 那么先取者必输。
因此我们可以写出对应的程序(默认 n m都大于0)
int Bash_Game(int n,int m)
//是否先手有必赢策略
{
if (n%(m+1)!=0) return 1;
return 0;
}
练习
1066 Bash游戏
模板题目
1067 Bash游戏 V2
思路:
通过打标找规律。
首先,n时A必胜等价于n-1,n-3,n-4时必败情况,再根据初始条件1胜2败3胜4胜 推导出周期是7,且2与7时必败
代码:
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
if (n % 7 == 0 || n % 7 == 2)
{
cout << "B" << endl;
}
else
{
cout << "A" << endl;
}
}
return 0;
}
1068 Bash游戏 V3
思路:
打表找规律。
打表发现3的倍数时为必败点,B赢。如果不是3的倍数可以-1或-2转移到3的倍数,必胜。
代码:
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f;
void slove()
{
int n=0;
string s;cin>>s;
for(char t: s){
int k=t-'0';
n+=k;
}
if(n%3)cout<<"A"<<endl;
else cout<<"B"<<endl;
}
signed main()
{
int t;
cin >> t;
while (t--)
slove();
return 0;
}
尼姆博弈(Nimm Game)
题目: 当有N堆,每堆有Mi>0个物品,依旧是两个人来取该怎么判断?
int Nimm_Game(int n)//假设n个数存在数组f[]中,有必胜策略返回1
{
int flag=0;
for(int i=1;i<=n;i++)
flag^=f[i];
if(flag) return 1;
return 0;
}
威佐夫博奕(Wythoff Game)
问题: 有两堆各若干个物品,
两个人轮流从某一堆取同样多的物品
--或 --同时从两堆中取同样多的物品,
规定每次至少取一个,多者不限,最后取光者得胜。
下面就没学
作 者:Angel_Kitty
出 处:https://www.cnblogs.com/ECJTUACM-873284962/
关于作者:阿里云ACE,目前主要研究方向是Web安全漏洞以及反序列化。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力!
实战.
B - Road to Arabella
Gym - 102263B
题意:
-
给一个n和k,
-
每个人每次可以1~data=max(1,n-k)中选一个数,然后把n减去这个数。(n实时更新)
-
n为0时游戏结束。
思路: -
当data=1时,只能选1。只要这时的n是偶数,那么每次轮到Ayoub选时n都是奇数,轮到Kilani选时n都是奇数,那么Ayoub是必胜的。
-
当n>k+1时Kilani永远是必胜态,因为他可以让自己选的时候n为奇数。
#include<iostream>
using namespace std;
int main() {
int t, n, k;
int flag;
cin >> t;
while (t--) {
flag = 1;
cin >> n >> k;
if ((n - k == 1 )||(n-k==0))
if(n % 2 == 0)flag = 0;
if (flag)cout << "Kilani\n";
else cout << "Ayoub\n";
}
}
B - Reverse Game
题意:
给出一段二进制序列,Alice和Bob可以依次轮流对10,110,100,1010进行反转,使其变成01,011,001,0101。Alice先开始操作,先无法操作的输。
思路:
1数量减少1或2。,这道题就变成了一个巴什博奕,将逆序对mod 3即可得到答案.
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int N = 5e5 + 10;
string s;
int n;
int ans,cnt;
signed main(){
cin>>s;
n=s.length();
int flag=0;
for(int i = s.size() - 1; i >= 0; i --){
if(s[i] == '0') cnt ++;
else ans += cnt;
}
if(ans%3)cout<<"Alice"<<endl;
else cout<<"Bob"<<endl;
return 0;
}