暑期集训 Day2 简单博弈论
暑训令人绝望的第二个日子。。今天是博弈论入门
几个要点:
Nim-sum:Nim博弈中,状态(x1,x2...xn)为必败态当且仅当(x1,x2...xn)异或和为0
SG函数:定义SG(x)=mex(S),其中S是x所有后继状态的集合,mex(S)表示不在S中的最小非负整数
SG定理:组合游戏中,游戏和的SG函数等于各子游戏SG函数的Nim-sum
巴什博奕:有n个石子,两人轮流取,每人最多取m个。n%(m+1)=0时后手必胜,否则先手必胜
斐波那契博弈:有n个石子,两人轮流取,第一次可以取1~n-1个,以后每次最多取上次取数的两倍。当n为斐波那契数时是必败态
优质博客:
SG函数详解+例题 https://blog.csdn.net/strangedbly/article/details/51137432
题目:
A.
You and your friend are playing a game in which you and your friend take turns removing stones from piles. Initially there are N piles with a1, a2, a3, . . . , aN number of stones. On each turn, a player must remove at least one stone from one pile but no more than half of the number of stones in that pile. The player who cannot make any moves is considered lost. For example, if there are three piles with 5, 1 and 2 stones, then the player can take 1 or 2 stones from first pile, no stone from second pile, and only 1 stone from third pile. Note that the player cannot take any stones from the second pile as 1 is more than half of 1 (the size of that pile). Assume that you and your friend play optimally and you play first, determine whether you have a winning move. You are said to have a winning move if after making that move, you can eventually win no matter what your friend does.
Input
The first line of input contains an integer T (T ≤ 100) denoting the number of testcases. Each testcase begins with an integer N (1 ≤ N ≤ 100) the number of piles. The next line contains N integers a1, a2, a3, . . . , aN (1 ≤ ai ≤ 2 ∗ 1018) the number of stones in each pile.
Output
For each testcase, print ‘YES’ (without quote) if you have a winning move, or ‘NO’ (without quote) if you don’t have a winning move.
Sample Input
4
2
4 4
3
1 2 3
3
2 4 6
3
1 2 1
Sample Output
NO
YES
NO
YES
SG函数裸题,打表找规律,可以发现:当n能被2整除时,SG(n)=n/2,不能被整除时,SG(n)=SG(n/2)
#include <iostream> typedef long long ll; using namespace std; ll SG(ll n){ if (n % 2 == 0) return n / 2; else return SG(n / 2); } int main(){ ll T, t, n, r; cin >> T; while (T--){ cin >> n; r = 0; for (int i = 0; i < n; i++){ cin >> t; r ^= SG(t); } if (r == 0) cout << "NO" << endl; else cout << "YES" << endl; } return 0; }
B.
选拔规则如下:
1、最初的捐款箱是空的;
2、两人轮流捐款,每次捐款额必须为正整数,并且每人每次捐款最多不超过m元(1<=m<=10)。
3、最先使得总捐款额达到或者超过n元(0<n<10000)的一方为胜者,则其可以亲赴灾区服务。
我们知道,两人都很想入选志愿者名单,并且都是非常聪明的人,假设林队先捐,请你判断谁能入选最后的名单?
Input
输入数据首先包含一个正整数C,表示包含C组测试用例,然后是C行数据,每行包含两个正整数n,m,n和m的含义参见上面提到的规则。
Output
对于每组测试数据,如果林队能入选,请输出字符串"Grass", 如果徐队能入选,请输出字符串"Rabbit",每个实例的输出占一行。
Sample Input
2
8 10
11 10
Sample Output
Grass
Rabbit
巴什博奕裸题,思路如下:
1.当剩下的石子为m+1时,无论先手取多少个,后手都可以拿完剩下的,因此n=m+1是后手必胜态
2.所以,如果n能被m+1整除,只要每次先手取k个,后手取m+1-k个,就一定能达到n=m+1的状态,后手必胜
3.如果n不被m+1整除,只要先手取一个n%(m+1),即可使剩余石子数能被m+1整除,先手必胜
#include <iostream> using namespace std; int main(){ int t, n, m; cin >> t; while (t--){ cin >> n >> m; if (n % (m + 1) == 0) cout << "Rabbit" << endl; else cout << "Grass" << endl; } return 0; }
C.
要种田得有田才行,Lele听说街上正在举行一场别开生面的拍卖会,拍卖的物品正好就是一块20亩的田地。于是,Lele带上他的全部积蓄,冲往拍卖会。
后来发现,整个拍卖会只有Lele和他的死对头Yueyue。
通过打听,Lele知道这场拍卖的规则是这样的:刚开始底价为0,两个人轮流开始加价,不过每次加价的幅度要在1~N之间,当价格大于或等于田地的成本价 M 时,主办方就把这块田地卖给这次叫价的人。
Lele和Yueyue虽然考试不行,但是对拍卖却十分精通,而且他们两个人都十分想得到这块田地。所以他们每次都是选对自己最有利的方式进行加价。
由于Lele字典序比Yueyue靠前,所以每次都是由Lele先开始加价,请问,第一次加价的时候,
Lele要出多少才能保证自己买得到这块地呢?
Input
本题目包含多组测试,请处理到文件结束(EOF)。每组测试占一行。
每组测试包含两个整数M和N(含义见题目描述,0<N,M<1100)
Output
对于每组数据,在一行里按递增的顺序输出Lele第一次可以加的价。两个数据之间用空格隔开。
如果Lele在第一次无论如何出价都无法买到这块土地,就输出"none"。
Sample Input
4 2 3 2 3 5
Sample Output
1 none 3 4 5
巴什博奕,保证留给对方的n可以被m+1整除即可
#include <iostream> #include <vector> using namespace std; vector<int> r; int main(){ int n, m; while (cin >> m >> n){ if (m % (n + 1) == 0) cout << "none" << endl; else{ r.clear(); for (int i = 1; i <= n; i++){ if ((m - i) % (n + 1) == 0 || i > m) r.push_back(i); } for (int i = 0; i < r.size(); i++){ if (i == r.size() - 1) cout << r[i] << endl; else cout << r[i] << ' '; } } } return 0; }
E.
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。
Good luck in CET-4 everybody!
Input
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。
Output
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。
Sample Input
1 3
Sample Output
Kiki Cici
#include <iostream> using namespace std; int main(){ int n; while (cin >> n){ if (n % 3 == 0) cout << "Cici" << endl; else cout << "Kiki" << endl; } return 0; }
G.
Input
输入有多组.每组第1行是2<=n<2^31. n=0退出.
Output
先取者负输出"Second win". 先取者胜输出"First win". 参看Sample Output.
Sample Input
2 13 10000 0
Sample Output
Second win Second win First win
斐波那契博弈,打表找规律可以发现必败态是斐波那契数,证明我等渣渣看不懂orz。。。。
打表:输出n=2-99时的情况,状态[i][j]记录n=i且上次取j个时的输赢
#include <iostream> #include <string.h> #define NMAX 100 using namespace std; bool vis[NMAX][NMAX];//[i][j]n=i且上次取j个的输赢 bool r[NMAX]; int main(){ int n, i, j, k; for (i = 0; i < NMAX; i++){ for (j = 0; j < NMAX; j++) vis[i][j] = 1; } for (i = 2; i < NMAX; i++){ r[i] = 0;
//n=i时的输赢 for (j = 1; j < i; j++){ if (!vis[i-j][j]){ r[i] = 1; break; } }
//更新[i][n](n<i/2)的状态 for (j = 1; j*2 < i; j++){ for (int k = 1; k <= j*2; k++){ vis[i][j] = 0; if (!vis[i-k][k]){ vis[i][j] = 1; break; } } } } for (i = 2; i < NMAX; i++) cout << i << ' ' << r[i] << endl; return 0; }
解题:
#include <iostream> #include <map> typedef long long ll; using namespace std; map<ll, bool> fab; void cal_fab(ll a, ll b){ if (a + b > ((1 << 31) - 1)) return; fab[a + b] = true; cal_fab(b, a + b); } int main(){ ll n = 0; cal_fab(0, 1); while (cin >> n){ if (n == 0) return 0; if (fab.count(n)) cout << "Second win" << endl; else cout << "First win" << endl; } }
K.
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧
陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐
如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
Input
输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1<M<=100),表示扑克牌的堆数,紧接着一行包含M个整数Ni(1<=Ni<=1000000,i=1…M),分别表示M堆扑克的数量。M为0则表示输入数据的结束。
Output
如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。
Sample Input
3 5 7 9 0
Sample Output
1
Nim博弈,改变一张扑克牌的数字,使所有数字的异或和为0.
由于仅存在 n xor n=0,所以改变后的扑克牌数量必须等于剩余数字的异或和,而扑克牌数量只可减少不可增加,统计ai > a1^a2...ai-1^ai+1...an-1^an的情况数即可
#include <iostream> using namespace std; int main(){ int n, i, j, t, c, num[101]; while (cin >> n){ if (n == 0) return 0; for (i = 0; i < n; i++) cin >> num[i]; c = 0; for (i = 0; i < n; i++){ t = 0; for (j = 0; j < n; j++){ if (i == j) continue; t ^= num[j]; } if (num[i] > t) c++; } cout << c << endl; } return 0; }
滚蛋了,睡觉了,剩下题后面补