博弈论

博弈论

  • SG函数
    • SG函数的定义
    • SG函数的和
    • 定理
    • get_GS
    • 例题
      • Fibonacci again and again
      • Good Luck in CET-4 Everybody!
      • Stone Game II hoj4388
  • 巴什博奕(Bash Game)
    • 例题
      • HDU 1846 Brave Game
  • 威佐夫博弈(Wythoff Game)
  • 斐波那契博弈
  • 尼姆博奕(Nimm Game)
    • 例题
      • POJ 2234 Matches Game
      • HDU1907 John
      • HDU1536 S-Nim
      • A Multiplication Game
      • Being a Good Boy in Spring Festival
      • Rabbit and Grass
      • A Chess Game
  • Anti_min问题
    • 模板
    • 例题
      • HDU 2509 Be the Winner
  • 对称博弈
      • A Funny Game

SG函数

SG函数的定义

先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

SG函数的和

游戏的和的SG函数值等于他包含的各个子游戏SG函数值的异或和

定理

游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0
游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0

get_GS

//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
     
    int i,j;
    memset(SG,0,sizeof(SG));
    //因为SG[0]始终等于0,所以i从1开始
    for(i = 1; i <= n; i++){
     
        //每一次都要将上一状态 的 后继集合 重置
        memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
            S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
        for(j = 0;; j++) if(!S[j]){
        //查询当前后继状态SG值中最小的非零值
            SG[i] = j;
            break;
        }
    }
}

例题

Fibonacci again and again

Problem Description任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;

假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。

Input
输入数据包含多个测试用例,每个测试用例占一行,包含3个整数m,n,p(1<=m,n,p<=1000)。
m=n=p=0则表示输入结束。

Output
如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。

Sample Input

1 1 1
1 4 1
0 0 0

Sample Output

Fibo
Nacci 

标程

#include 
#include 
#define MAXN 1000 + 10
#define N 20
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
     
    int i,j;
    memset(SG,0,sizeof(SG));
    for(i = 1; i <= n; i++){
     
     memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
        S[SG[i-f[j]]] = 1;
        for(j = 0;;j++) if(!S[j]){
     
        SG[i] = j;
        break;
        }
     }
 }
int main()
{
     
    int n,m,k;
    f[0] = f[1] = 1;
    for(int i = 2; i <= 16; i++)
        f[i] = f[i-1] + f[i-2];
    getSG(1000);     
    while(scanf("%d%d%d",&m,&n,&k),m||n||k)
    {
     
    if(SG[n]^SG[m]^SG[k]) printf("Fibo\n");//问题的和=子问题SG函数异或和 
    else printf("Nacci\n");
     }
    return 0;
}

Good Luck in CET-4 Everybody!

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 18791 Accepted Submission(s): 11771

Problem Description
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的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能有好的状态。

Input
输入数据包含多个测试用例,每个测试用例占一行,包含一个整数n(1<=n<=1000)。

Output
如果Kiki能赢的话,请输出“Kiki”,否则请输出“Cici”,每个实例的输出占一行。

Sample Input

1
3

Sample Output

Kiki
Cici

Author
lcy

#include 
#include 
#define MAXN 1000 + 10
#define N 200
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
     
    int i,j;
    memset(SG,0,sizeof(SG));
    for(i = 1; i <= n; i++){
     
     memset(S,0,sizeof(S));
        for(j = 0; f[j] <= i && j <= N; j++)
        S[SG[i-f[j]]] = 1;
        for(j = 0;;j++) if(!S[j]){
     
        SG[i] = j;
        break;
        }
     }
 }
int main()
{
     
    int n;
    f[0] =1;
    for(int i = 1; i <= 16; i++)
        f[i] = f[i-1]*2;
    getSG(1005);     
    while(scanf("%d",&n)!=EOF)
    {
     
    if(SG[n]) printf("Kiki\n");
    else printf("Cici\n");
    }
    return 0;
}

Stone Game II hoj4388

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 785 Accepted Submission(s): 464

Problem Description   
Stone Game II comes. It needs two players to play this game. There are some piles of stones on the desk at the beginning. Two players move the stones in turn. At each step of the game the player should do the following operations.
First, choose a pile of stones. (We assume that the number of stones in this pile is n)
Second, take some stones from this pile. Assume the number of stones left in this pile is k. The player must ensure that 0 < k < n and (k XOR n) < n, otherwise he loses.
At last, add a new pile of size (k XOR n). Now the player can add a pile of size ((2*k) XOR n) instead of (k XOR n) (However, there is only one opportunity for each player in each game).
The first player who can’t do these operations loses. Suppose two players will do their best in the game, you are asked to write a program to determine who will win the game.

Input
  
The first line contains the number T of test cases (T<=150). The first line of each test cases contains an integer number n (n<=50), denoting the number of piles. The following n integers describe the number of stones in each pile at the beginning of the game.
You can assume that all the number of stones in each pile will not exceed 100,000.

Output   
For each test case, print the case number and the answer. if the first player will win the game print “Yes”(quotes for clarity) in a single line, otherwise print “No”(quotes for clarity).

Sample Input

3
2
1 2
3
1 2 3
4
1 2 3 3 


Sample Output

Case 1: No
Case 2: Yes
Case 3: No

题目大意:
给出n堆物品,每堆物品都有若干件,现在A和B进行游戏,每人每轮操作一次,按照如下规则:

  1. 任意选择一个堆,假设该堆有x个物品,从中选择k个,要保证0
  2. 再增加一个大小为x^ k的堆(也就相当于将一个x个物品的堆变成一个k个物品的堆和一个x^ k个物品的堆),另外有一个技能,可以将这个大小为x^ k的堆变成(2*k)^x的堆,但是这个技能每个人只有一次机会可以使用。
  3. 现在问两人轮流操作,都采取最优策略,最后不能操作的人输,问谁会赢。

解题思路
把每堆原来a个物品分成两堆,k和k^ a,证明得,这样分堆,不会影响二进制中1的总数的奇偶性,(后面会给出证明)
为什么要考虑二进制呢?注意题目中的条件,x^ k 设一堆数目中二进制表示1的个数是m,则所有要操作的步骤就是所有的(m-1)加起来。讨论这个总数的奇偶性就行了。


如何统计二进制中1的个数?可以对2不断取模进行,也可以使用n&(n-1)

#include
#include
#include
#include
#include
#include
using namespace std;
int m;
int main(){
     
    int T;
    scanf("%d",&T);
    for(int j=1;j<=T;j++){
     
        scanf("%d",&m);
        int n=0,x;
        for(int i=1;i<=m;i++){
     
            scanf("%d",&x);
            while(x){
     
                n+=(x&1);
                x/=2;
            }
        }
  		n+=m;
        printf("Case %d: ",j);
        if(n&1){
     
            printf("Yes\n");
        }
        else{
     
            printf("No\n");
        }
    }
    return 0;
}
#include 
#define ll long long
#define mod 1000000007
using namespace std;

int getsg(int n) 
{
     
    int count=0;
    while(n)
    {
     
        n&=(n-1);
        count++;
    }
    return count;
}
int main()
{
     
    int T,n;
    cin>>T;
    int index=0;
    while(T--)
    {
     
        cin>>n;
        int ans=0,temp;
        for(int i=1;i<=n;++i)
		{
     
			cin>>temp;
			ans+=(getsg(temp)-1);
		} 
		printf("Case %d: ",++index);
        if(ans&1) puts("Yes");
        else puts("No");
    }
    return 0;
}

巴什博奕(Bash Game)

只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。

显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,
后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的法则:如果
n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走
k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的
取法,那么先取者肯定获胜。

总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十
个,谁能报到100者胜。

#include 
using namespace std;
int main()
{
     
    int n,m;
    while(cin>>n>>m)
      if(n%(m+1)==0)  cout<<"后手必胜"<<endl;
      else cout<<"先手必胜"<<endl;
    return 0;
}

例题

HDU 1846 Brave Game

Problem Description
十年前读大学的时候,中国每年都要从国外引进一些电影大片,其中有一部电影就叫《勇敢者的游戏》(英文名称:Zathura),一直到现在,我依然对于电影中的部分电脑特技印象深刻。
今天,大家选择上机考试,就是一种勇敢(brave)的选择;这个短学期,我们讲的是博弈(game)专题;所以,大家现在玩的也是“勇敢者的游戏”,这也是我命名这个题目的原因。
当然,除了“勇敢”,我还希望看到“诚信”,无论考试成绩如何,希望看到的都是一个真实的结果,我也相信大家一定能做到的~

各位勇敢者要玩的第一个游戏是什么呢?很简单,它是这样定义的:
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;

如果游戏的双方使用的都是最优策略,请输出哪个人能赢。

Input
输入数据首先包含一个正整数C(C<=100),表示有C组测试数据。
每组测试数据占一行,包含两个整数n和m(1<=n,m<=1000),n和m的含义见题目描述。

Output
如果先走的人能赢,请输出“first”,否则请输出“second”,每个实例的输出占一行。

Sample Input

2
23 2
4 3
 
Sample Output
first
second
#include 
using namespace std;
int main()
{
     
    int n,m,t;
    cin>>t;
    while(t--){
     
    	cin>>n>>m;
    	if(n%(m+1)==0)  cout<<"second"<<endl;
    	else cout<<"first"<<endl;
    }  
    return 0;
}

威佐夫博弈(Wythoff Game)

有两堆各若干的物品,两人轮流从其中一堆取至少一件物品,至多不限,或从两堆中同时取相同件物品,规定最后取完者胜利。

直接说结论了,若两堆物品的初始值为(x,y),且x

记w=(int)[((sqrt(5)+1)/2)*z ];

若w=x,则先手必败,否则先手必胜。

#include 
#include 
#include 
using namespace std;
int main()
{
     
    int n1,n2,temp;
    while(cin>>n1>>n2)
    {
     
        if(n1>n2)  swap(n1,n2);
        temp=floor((n2-n1)*(1+sqrt(5.0))/2.0);
        if(temp==n1) cout<<"后手必胜"<<endl;
        else cout<<"先手必胜"<<endl;
    }
    return 0;
}

斐波那契博弈

有一堆物品,两人轮流取物品,先手最少取一个,至多无上限,但不能把物品取完,之后每次取的物品数不能超过上一次取的物品数的二倍且至少为一件,取走最后一件物品的人获胜。

结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)

#include
#include
using namespace std;
int f[100];
void Init()//斐波那契数列
{
     
    f[0]=f[1]=1;
    for(int i=2;i<=100;i++)
    {
     
        f[i]=f[i-1]+f[i-2];
    }
}
 
int main()
{
     
    int n;
    cin>>n;
    Init();
    bool flag=false;
    for(int i=0;f[i];i++)
    {
     
        if(f[i]==n)
        {
     
            flag=true;
            break;
        }
    }
    if(flag)
        cout<<"后手必胜"<<endl;
    else
        cout<<"先手必胜"<<endl;
}

尼姆博奕(Nimm Game)

有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件,取到最后一件物品的人获胜。

结论就是:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败,否则先手必胜。

代码如下:

#include 
#include 
#include 
using namespace std;
int main()
{
     
    int n,ans,temp;
    while(cin>>n)
    {
     
        temp=0;
        for(int i=0;i<n;i++)
        {
     
            cin>>ans;
            temp^=ans;
        }
        if(temp==0)  cout<<"后手必胜"<<endl;
        else cout<<"先手必胜"<<endl;
    }
    return 0;
}

例题

POJ 2234 Matches Game

Description
Here is a simple game. In this game, there are several piles of matches and two players. The two player play in turn. In each turn, one can choose a pile and take away arbitrary number of matches from the pile (Of course the number of matches, which is taken away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who plays first can win the game or not.
Input
The input consists of several lines, and in each line there is a test case. At the beginning of a line, there is an integer M (1 <= M <=20), which is the number of piles. Then comes M positive integers, which are not larger than 10000000. These M integers represent the number of matches in each pile.
Output
For each test case, output “Yes” in a single line, if the player who play first will win, otherwise output “No”.

Sample Input

2 45 45
3 3 6 9

Sample Output

No
Yes

当前为必败局面 ,当且仅当pile[1]^ pile[2] ^ …pile[n]=0

#include 
using namespace std;
int main()
{
     
 int M;
 while(cin>>M,M)
 {
     
  int pile[20];
  for(int i=0; i<M; ++i)
  {
      
   cin>>pile[i]; 
  }
  int result = 0;
  for(int i=0; i<M; ++i)
  {
      
   result ^= pile[i]; 
  }
  if(result)
  cout<<"Yes"<<endl;
  else      
  cout<<"No" <<endl;
 }
 return 0;
}

HDU1907 John

Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)
Total Submission(s): 2577 Accepted Submission(s): 1400

Problem Description
Little John is playing very funny game with his younger brother. There is one big box filled with M&Ms of different colors. At first John has to eat several M&Ms of the same color. Then his opponent has to make a turn. And so on. Please note that each player has to eat at least one M&M during his turn. If John (or his brother) will eat the last M&M from the box he will be considered as a looser and he will have to buy a new candy box.

Both of players are using optimal game strategy. John starts first always. You will be given information about M&Ms and your task is to determine a winner of such a beautiful game.

Input
The first line of input will contain a single integer T – the number of test cases. Next T pairs of lines will describe tests in a following format. The first line of each test will contain an integer N – the amount of different M&M colors in a box. Next line will contain N integers Ai, separated by spaces – amount of M&Ms of i-th color.

Constraints:
1 <= T <= 474,
1 <= N <= 47,
1 <= Ai <= 4747

Output
Output T lines each of them containing information about game winner. Print “John” if John will win the game or “Brother” in other case.

Sample Input

2
3
3 5 1
1
1

Sample Output

John
Brother

这道题有些不一样,如果John吃的是某个盒子最后一颗,那就判定John为败。
所以,这道题分为两种情况讨论:
①若所有堆的数量都为1。则根据奇偶来判断谁胜。
②其他情况,将所有数据异或起来,判断是否为奇异态。


#include 
#include 
using namespace std;
int arr[48];
int main()
{
     
    int t,n,i,temp;
    cin>>t;
    while( t-- )
    {
     
        cin>>n;
        for(i=0;i<n;++i)
            cin>>arr[i];
        sort(arr,arr+n);
        // 如果全是1,按照奇偶判断谁获胜
        if( arr[n-1]==1 )
        {
     
            if( n&1 )   cout<<"Brother"<<endl;
            else    cout<<"John"<<endl;
            continue;
        }
        // 异或加起来
        temp=0;
        for(i=0;i<n;++i)
            temp^=arr[i];
        if( temp==0 )   cout<<"Brother"<<endl;
        else    cout<<"John"<<endl;
    }
    return 0;
}

HDU1536 S-Nim

Understandibly it is no fun to play a game when both players know how to play perfectly (ignorance is bliss). Fourtunately, Arthur and Caroll soon came up with a similar game, S-Nim, that seemed to solve this problem. Each player is now only allowed to remove a number of beads in some predefined set S, e.g. if we have S =(2, 5) each player is only allowed to remove 2 or 5 beads. Now it is not always possible to make the xor-sum 0 and, thus, the strategy above is useless. Or is it?

your job is to write a program that determines if a position of S-Nim is a losing or a winning position. A position is a winning position if there is at least one move to a losing position. A position is a losing position if there are no moves to a losing position. This means, as expected, that a position with no legal moves is a losing position.

Input
Input consists of a number of test cases. For each test case: The first line contains a number k (0 < k ≤ 100 describing the size of S, followed by k numbers si (0 < si ≤ 10000) describing S. The second line contains a number m (0 < m ≤ 100) describing the number of positions to evaluate. The next m lines each contain a number l (0 < l ≤ 100) describing the number of heaps and l numbers hi (0 ≤ hi ≤ 10000) describing the number of beads in the heaps. The last test case is followed by a 0 on a line of its own.

Output
For each position: If the described position is a winning position print a ‘W’.If the described position is a losing position print an ‘L’. Print a newline after each test case.

Sample Input

2 2 5
3
2 5 12
3 2 4 7
4 2 3 7 12
5 1 2 3 4 5
3
2 5 12
3 2 4 7
4 2 3 7 12
0
 

Sample Output

LWW
WWL

题目大意:
就是首先给你一个数s,输入s个数的集合,接下来的操作只能取与这集合相对应的石子,接下来一行输入一个数m,表示
m次操作,然后输入一个n,表示有n堆石子,如果先手胜出输出L,否则输出W,当然得一连串输出这m个字母。

思路:运用sg函数和xor处理

#include
#include
#include
#include
using namespace std;
const int maxn=10010;
int sg[maxn],oper[maxn],temp[maxn],k[maxn];
int s;
void getsg()
{
     
	memset(sg,0,sizeof(sg));
	for(int i=0;i<10010;i++){
     
		memset(temp,0,sizeof(temp));
		for(int j=0;j<s&&oper[j]<=i;j++){
     
			temp[sg[i-oper[j]]]=1;//如果到达的前一点均为必胜,那么这个点为必败 
		}                        //否则为必胜 
		for(int j=0;j<=s;j++){
     //深入了解你会发现sg会使一个周期 
			if(temp[j]==0){
     //如果都出现了,那么不会进行赋值了,为必败 
				sg[i]=j;//如果有第一个必败点,那么记录下来,这个点变为必胜点 
				break;
			}
		}
	}
}
int main()
{
     
	char p[1005];
	int m,a,b,c;
	string str;
	while(scanf("%d",&s),s){
     
		for(int i=0;i<s;i++) scanf("%d",&oper[i]);
		getsg();
		scanf("%d",&m);
		for(int i=0;i<m;i++){
     
		    scanf("%d",&a);
		    scanf("%d",&c);
		    c=sg[c];//异或操作就是将每堆石子进行异或, 
		    for(int j=1;j<a;j++){
     
			    scanf("%d",&b);
			    c=c^sg[b]; 
		    }
			if(c==0) p[i]='L';如果异或值为零 
			else p[i]='W';					
		}
		printf("%s\n",p); 
	} 
} 

A Multiplication Game

Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 8593 Accepted Submission(s): 4819

Problem Description
Stan and Ollie play the game of multiplication by multiplying an integer p by one of the numbers 2 to 9. Stan always starts with p = 1, does his multiplication, then Ollie multiplies the number, then Stan and so on. Before a game starts, they draw an integer 1 < n < 4294967295 and the winner is who first reaches p >= n.

Input
Each line of input contains one integer number n.

Output
For each line of input output one line either

Stan wins.

or

Ollie wins.

assuming that both of them play perfectly.

Sample Input

162
17
34012226

Sample Output

Stan wins.
Ollie wins.
Stan wins.

必败点(P点) :前一个选手(Previous player)将取胜的位置称为必败点。
必胜点(N点) :下一个选手(Next player)将取胜的位置称为必胜点。

对于这两个概念的描述,我开始的时候也搞不懂。
其实可以从字面理解,简单说来,就是当你走到某一点的时候,如果你无论怎么走也不能赢对方,此时你必败,这个点就叫做必败点。

算法实现:

步骤1:将所有终结位置标记为必败点(P点);(终结位置指的是不能将游戏进行下去的位置)
步骤2:将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4:如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2。

好了,下面通过这道题来理解下吧。

题意:

你和一个人玩游戏,给你一个数字n,每次操作可以从2~9中任选一个数字,并把它与p相乘,(游戏开始时p=1)

两人轮流操作,当一个人操作完后p>=n,这个人就是胜者。

解题思路:
由于每次都是从p=1开始的,所以只要判断每个游戏中1为必败点还是必胜点即可。(以下各式 / 均为取上整)
依照上面所提到的算法,将终结位置,即[n,无穷]标记为必败点;
然后将所有一步能到达此必败段的点标记为必胜点,即[n/9,n-1]为必胜点;
然后将只能到达必胜点的点标记为必败点,即[n/9/2,n/9-1]为必败点;
重复上面2个步骤,直至可以确定1是必胜点还是必败点。

#include 
#include 
int main()
{
     
    unsigned int n,x;
    while(~scanf("%u",&n))
    {
     
        for(x=0;n>1;x++)
        {
     
            if(x&1)//x&1实现两个操作交替运行
                n = ceil(n*1.0/2);
            else
                n = ceil(n*1.0/9);
        }
        puts(x&1?"Stan wins.":"Ollie wins.");
    }
	return 0;
}

ceil()函数的使用
博弈论_第1张图片

// 假设最终数为 162,为了防止对方得到大于等于 162的数,则上一步,一方应该让数尽量不大于 162 / 
// 9 = 18,如果大于等于 18,则对方可以乘以 9 即可大于等于 162,再往上一步,一方应该尽量选择将数
// 相乘后得到大于等于 9 的数,因为这样,另一方无论选择那个乘数,终将导致乘积大于等于 18,因为 9 /
// 9 = 1,即谁先乘,谁将获胜。
// 对于给定的数N,对于 Stan 来说,先达到数 N,则是胜利,回归到上一步,如果 Stan 先达到 N / 9,
// 则失败,继续上一步,若 Stan 先达到 N / 9 / 2,则胜利,使用递归解决即可。
#include 
#include 
using namespace std;
void ones(int number, bool win)
{
     
	if (number <= 9 && win)
	{
     
		cout << "Stan wins." << endl;
		return;
	}
	if (number <= 2 && !win)
	{
     
		cout << "Ollie wins." << endl;
		return;
	}
	if (win)
		ones(ceil(number / 9.0), !win);
	else
		ones(ceil(number / 2.0), !win);
}
int main(int ac, char *av[])
{
     
	int number;
	while (cin >> number)
		ones(number, true);
	return 0;
}

Being a Good Boy in Spring Festival

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 11882 Accepted Submission(s): 7319

Problem Description
一年在外 父母时刻牵挂
春节回家 你能做几天好孩子吗
寒假里尝试做做下面的事情吧

陪妈妈逛一次菜场
悄悄给爸爸买个小礼物
主动地 强烈地 要求洗一次碗
某一天早起 给爸妈用心地做回早餐

如果愿意 你还可以和爸妈说
咱们玩个小游戏吧 ACM课上学的呢~

下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”

Input
输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1

Output
如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。

Sample Input

3
5 7 9
0
 

Sample Output

1

思路:可选步数为任意步,SG(x) = x;
本题中每一堆都可以选任意个,所以每一堆的SG值都是所剩余的个数。
最后结果是所有堆的SG值异或的结果。令ans = 所有堆的SG值异或的结果
如果ans == 0,则是必败点。
如果ans != 0,使取后结果为0的策略是必胜策略
具体怎么取呢?
每一堆的数值与ans相异或,所得的结果就是这一堆可以取的数量。
但是,如要这一堆数量没有这么多,就不可以这么取


#include   
using namespace std;  
int value[101];  
int main ()  
{
       
    int n,ans,count,i;  
    while (cin>>n && n)  
    {
       
        ans=0;  
        for (i=0;i<n;i++)  
        {
       
            cin>>value[i];  
            ans^=value[i];  
        }  
        count=0;  
        for (i=0;i<n;i++)  
        {
       
            ans^=value[i];  //用到了一个很明显的结论:a = a ^ b ^ b;
            if (value[i]>ans) count++; //当前堆有足够多的牌,方案数++
            ans^=value[i];  
        }  
        cout<<count<<endl;  
    }  
    return 0;  
}

Rabbit and Grass

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1088 Accepted Submission(s): 816

Problem Description
大学时光是浪漫的,女生是浪漫的,圣诞更是浪漫的,但是Rabbit和Grass这两个大学女生在今年的圣诞节却表现得一点都不浪漫:不去逛商场,不去逛公园,不去和AC男约会,两个人竟然猫在寝食下棋……
说是下棋,其实只是一个简单的小游戏而已,游戏的规则是这样的:
1、 棋盘包含1*n个方格,方格从左到右分别编号为0,1,2,…,n-1;
2、 m个棋子放在棋盘的方格上,方格可以为空,也可以放多于一个的棋子;
3、 双方轮流走棋;
4、 每一步可以选择任意一个棋子向左移动到任意的位置(可以多个棋子位于同一个方格),当然,任何棋子不能超出棋盘边界;
5、 如果所有的棋子都位于最左边(即编号为0的位置),则游戏结束,并且规定最后走棋的一方为胜者。

对于本题,你不需要考虑n的大小(我们可以假设在初始状态,棋子总是位于棋盘的适当位置)。下面的示意图即为一个1*15的棋盘,共有6个棋子,其中,编号8的位置有两个棋子。

在这里插入图片描述

大家知道,虽然偶尔不够浪漫,但是Rabbit和Grass都是冰雪聪明的女生,如果每次都是Rabbit先走棋,请输出最后的结果。

Input
输入数据包含多组测试用例,每个测试用例占二行,首先一行包含一个整数m(0<=m<=1000),表示本测试用例的棋子数目,紧跟着的一行包含m个整数Ki(i=1…m; 0<=Ki<=1000),分别表示m个棋子初始的位置,m=0则结束输入。

Output
如果Rabbit能赢的话,请输出“Rabbit Win!”,否则请输出“Grass Win!”,每个实例的输出占一行。

Sample Input

2 
3 5
3
3 5 6
0

Sample Output

Rabbit Win!
Grass Win!

Author
lcy


距离转为NIM,将每一堆直接放在最左边

#include
#include
#include
using namespace std;
int main()
{
     
    int n,num;
    while(scanf("%d",&n)&&n)
    {
     
        int ans=0;
        for(int i=0;i<n;i++)
        {
     
            scanf("%d",&num);
            ans^=num;
        }
        if(ans) puts("Rabbit Win!");
        else puts("Grass Win!");
    }
 
}

A Chess Game

Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 5523 Accepted: 2139
Description

Let’s design a new chess game. There are N positions to hold M chesses in this game. Multiple chesses can be located in the same position. The positions are constituted as a topological graph, i.e. there are directed edges connecting some positions, and no cycle exists. Two players you and I move chesses alternately. In each turn the player should move only one chess from the current position to one of its out-positions along an edge. The game does not end, until one of the players cannot move chess any more. If you cannot move any chess in your turn, you lose. Otherwise, if the misfortune falls on me… I will disturb the chesses and play it again.

Do you want to challenge me? Just write your program to show your qualification!
Input

Input contains multiple test cases. Each test case starts with a number N (1 <= N <= 1000) in one line. Then the following N lines describe the out-positions of each position. Each line starts with an integer Xi that is the number of out-positions for the position i. Then Xi integers following specify the out-positions. Positions are indexed from 0 to N-1. Then multiple queries follow. Each query occupies only one line. The line starts with a number M (1 <= M <= 10), and then come M integers, which are the initial positions of chesses. A line with number 0 ends the test case.
Output

There is one line for each query, which contains a string “WIN” or “LOSE”. “WIN” means that the player taking the first turn can win the game according to a clever strategy; otherwise “LOSE” should be printed.
Sample Input

4
2 1 2
0
1 3
0
1 0
2 0 2
0

4
1 1
1 2
0
0
2 0 1
2 1 1
3 0 1 3
0

Sample Output

WIN
WIN
WIN
LOSE
WIN

题意:
先输入一个数字N,代表有N个方格。然后有N行,对于第i行,第一个数字M代表方格i能达到几个方格,然后M个数字分别是方格的编号。
在后面是问题,每行第一个数字S表示棋子的数目,后面S个数表示棋子的所在方格的编号。

输出win表示先手胜利,胜利条件是你走一步棋后,对方无棋可走。
解法:
如果光看一个石子的话,这就是sg函数的定义。然后对整个图处理一下每个点的sg值。对这M个点怎么处理,这么想吧。假设其中一个点为i,其sg值为sg[i] = a (a != 0)
那可以发现,i可以走向[0, a-1]的任意sg值,这相当与什么?相当与这里有一堆石子,共a个。M个点就是M堆石子,每堆石子的个数就是这个点的sg值。所以这就是NIM博弈的模型了

#include
#include
using namespace std;
 
int map[1010][1010];
int SG[1010];
int N;
 
//这是网上一个很好看的求SG值的办法
int DFS(int n) /* 典型求 SG 函数的办法 */
{
     
	int i;
    if(SG[n]!=-1) return SG[n];
    bool used[1010];
    memset(used,0,sizeof(used));
	for(i=0;i<N;i++)
    {
     
		if(map[n][i] != -1)
        used[DFS(i)]=true; //这里是DFS(i),表示i的SG值
    }
    i=0;
    while(used[i]) i++; //在这里算出n的SG值
    return SG[n]=i;
}
 
 
int main()
{
     
	int i,j,k,t;
	int X;
	int temp,ans;
	while(scanf("%d",&N) != EOF)
	{
     
		memset(map,255,sizeof(map));
		memset(SG,255,sizeof(SG));
		for(i=0;i<N;i++)
		{
     
			scanf("%d",&k);
			if(k == 0)
			{
     
				SG[i] = 0;
			}
			for(j=0;j<k;j++)
			{
     
				scanf("%d",&t);
				map[i][t] = 1;
			}
		}
		
		while(scanf("%d",&X) != EOF)
		{
     
			if(X == 0) break;
			ans = 0;
			for(i=0;i<X;i++)
			{
     
				scanf("%d",&temp);
				ans = ans ^DFS(temp);
			}
			if(ans != 0)
			{
     
				printf("WIN\n");
			}
			else
			{
     
				printf("LOSE\n");
			}
		}
	}
	return 0;
}

Anti_min问题

这种题与以往的博弈题的胜负条件不同,谁先走完最后一步谁输,但他也是一类Nim游戏,即为anti-nim游戏。

首先给出结论:先手胜当且仅当 ①所有堆石子数都为1且游戏的SG值为0(即有偶数个孤单堆-每堆只有1个石子数);②存在某堆石子数大于1且游戏的SG值不为0.

证明:

若所有堆都为1且SG值为0,则共有偶数堆石子,故先手胜。
i)只有一堆石子数大于1时,我们总可以对该石子操作,使操作后堆数为奇数且所有堆的石子数均为1;

ii)有超过一堆的石子数1时,先手将SG值变为0即可,且总还存在某堆石子数大于1
1
2
3
4
因为先手胜。

此题用到的概念:

【定义1】:若一堆中仅有一个石子,则被称为孤单堆。若大于1个,则称为充裕堆。

【定义2】:T态中,若充裕堆的堆数大于等于2,则称为完全利他态,用T2表示;若充裕堆的堆数等于0,则称为部分利他态。用T0表示。

孤单堆的根数异或智慧影响二进制的最后以为,但充裕堆会影响高位(非最后一位)。一个充裕堆,高位必有一位不为0,则所有根数异或不为0。故不会是T态。

【定理1】:S0态,即仅有奇数个孤单堆,必败。T0态必胜。

证明:S0态,其实就是每次只能取一根。每次第奇数根都由自己取,第偶数根都由对方取,所以最后一根必由自己取。所以必败。同理:T0态必胜。

【定理2】:S1态,只要方法正确,必胜。

证明:若此时孤单堆堆数为奇数,把充裕堆取完;否则,取成一根。这样,就变成奇数个孤单堆,由对方取。由定理1,对方必输,己必胜。

【定理3】:S2态不可转一次变为T0态。

证明:充裕堆数不可能一次由2变为0。

【定理4】:S2态可一次转变为T2态。

证明:因为对于任何一个S态,总能从一堆中取出若干个使之成为T态。又因为S1态,只要方法正确,必胜。S2态不可转一次变为T0态,所以转变的T态为T2态。

【定理5】:T2态,只能转变为S2态或S1态。

证明:因为T态,取任何一堆的若干根都将成为S态。由于充裕堆不可能一次由2变为0,所以此时的S态不可能为S0态。得证。

【定理6】:S2态,只要方法正确,必胜。

证明:方法如下:

S2态,就把它变为T2态。(定理4);
对方只能T2转变为S2态或S1态(定理5)。

若转变为S2,则转向①。

若转变为S1,这时己必胜(定理1)。
1
2
3
4
5
6
【定理7】:T2态必输。

证明:同定理6.

综上所述:必输态有:T2、S0;必胜态有:S2、S1、T0。


模板

#include
using namespace std;
int main()
{
     
    int i,n,m;
    while(cin >> n)
    {
     
              int flag = 0; //判断是否是孤单堆
              int s = 0;
              for(i = 0;i < n;i++) 
              {
     
                    cin >> m;
                    s ^= m;
                    if(m > 1) 
                         flag = 1;
              }
              if(flag == 0)
                      if(n % 2)
                           cout << "No\n";
                      else
                           cout << "Yes\n";
              else
                  if(s == 0)
                      cout << "No" <<endl;
                  else
                      cout << "Yes" <<endl;
    }
    return 0;
}

例题

HDU 2509 Be the Winner

Problem Description
Let’s consider m apples divided into n groups. Each group contains no more than 100 apples, arranged in a line. You can take any number of consecutive apples at one time.
For example “@@@” can be turned into “@@” or “@” or “@ @”(two piles). two people get apples one after another and the one who takes the last is
the loser. Fra wants to know in which situations he can win by playing strategies (that is, no matter what action the rival takes, fra will win).

Input
You will be given several cases. Each test case begins with a single number n (1 <= n <= 100), followed by a line with n numbers, the number of apples in each pile. There is a blank line between cases.

Output
If a winning strategies can be found, print a single line with “Yes”, otherwise print “No”.

Sample Input

2
2 2
1
3
 

Sample Output

No
Yes
 

题目大意:有n堆苹果,每堆有mi个。两人轮流取,每次可以从一堆苹果中取任意连续个苹果(取完后一堆可能变成了2堆),最后取光者为输。Fra先下,问是否可以获胜。

#include
using namespace std;
int main()
{
     
    int i,n,m;
    while(cin >> n)
    {
     
              int flag = 0; //判断是否是孤单堆
              int s = 0;
              for(i = 0;i < n;i++) 
              {
     
                    cin >> m;
                    s ^= m;
                    if(m > 1) 
                         flag = 1;
              }
              if(flag == 0)
                      if(n % 2)
                           cout << "No\n";
                      else
                           cout << "Yes\n";
              else
                  if(s == 0)
                      cout << "No" <<endl;
                  else
                      cout << "Yes" <<endl;
    }
    return 0;
}

对称博弈

A Funny Game

Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 8649 Accepted: 5320

Description

Alice and Bob decide to play a funny game. At the beginning of the game they pick n(1 <= n <= 106) coins in a circle, as Figure 1 shows. A move consists in removing one or two adjacent coins, leaving all other coins untouched. At least one coin must be removed. Players alternate moves with Alice starting. The player that removes the last coin wins. (The last player to move wins. If you can’t move, you lose.)
博弈论_第2张图片

Figure 1

Note: For n > 3, we use c1, c2, …, cn to denote the coins clockwise and if Alice remove c2, then c1 and c3 are NOT adjacent! (Because there is an empty place between c1 and c3.)

Suppose that both Alice and Bob do their best in the game.
You are to write a program to determine who will finally win the game.
Input

There are several test cases. Each test case has only one line, which contains a positive integer n (1 <= n <= 106). There are no blank lines between cases. A line with a single 0 terminates the input.
Output

For each test case, if Alice win the game,output “Alice”, otherwise output “Bob”.
Sample Input

1
2
3
0

Sample Output

Alice
Alice
Bob
Source

POJ Contest,Author:Mathematica@ZSU


思路:
对于n<=2,先手ALICE必胜。n>3时,无论Alice怎么取,BOB都可以取一定的数目来构造出中心对称,这样BOB一定是最后一次取光的。
当n > 3 时,无论A怎么选择,B可以选一个特殊的位置拿走1个连续的2个,将A取剩的环路剪成两条相同的链路,然后,无论A从那条链路中那个位置取几个,B都可以在另一条链路中采用相同的取法。此时,B慢A一步,即最后一个一定是B取到的,B必胜!

#include 
 
int main()
{
     
	int n;
	while(scanf("%d", &n), n){
     
		if(n <= 2)
			printf("Alice\n");
		else
			printf("Bob\n");
	}
 
	return 0;
}

你可能感兴趣的:(ACM,算法)