暑期集训 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.

对于四川同胞遭受的灾难,全国人民纷纷伸出援助之手,几乎每个省市都派出了大量的救援人员,这其中包括抢险救灾的武警部队,治疗和防疫的医护人员,以及进行心理疏导的心理学专家。根据要求,我校也有一个奔赴灾区救灾的名额,由于广大师生报名踊跃,学校不得不进行选拔来决定最后的人选。经过多轮的考核,形势逐渐明朗,最后的名额将在“林队”和“徐队”之间产生。但是很巧合,2个人的简历几乎一模一样,这让主持选拔的8600很是为难。无奈,他决定通过捐款来决定两人谁能入选。 
选拔规则如下: 
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始终没有逃过退学的命运,因为他没有拿到奖学金。现在等待他的,就是像FarmJohn一样的农田生涯。 

要种田得有田才行,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.

大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。 
“升级”?“双扣”?“红五”?还是“斗地主”? 
当然都不是!那多俗啊~ 
作为计算机学院的学生,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.

1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。取完者胜.先取者负输出"Second win".先取者胜输出"First win". 

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;
}

 

滚蛋了,睡觉了,剩下题后面补

 
posted @ 2018-08-03 02:25  Shepard  阅读(355)  评论(0编辑  收藏  举报