一、基础概念
1、奇异局势:指你一定会输的状态,又叫必败状态。
例如威佐夫博弈中的(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)、...... 、(ak,bk) (k = 0, 1, 2, 3,......)。
自己走路正确一定能赢得:(0,0),(1,k),(k,2),(1+k,2+k)
2、奇异局势的性质
(1):每个自然数都包含在且只包含在一个奇异局势中。
(2):对任意的奇异局势,任何合法的操作都会使其成为非奇异局势,也就是奇异局势的所有后继状态均为非奇异局势。
(3):任何非奇异局势都可以通过某种合法操作得到奇异局势,即奇异局势的所有后继状态中存在奇异局势。
若符合奇异局势,则先手输。否则先手胜。
二、威佐夫博弈(hdu1527)
对于(ak,bk),ak是未在前面出现过的最小自然数(不包括k的),bk=ak+k;
然后就得到了奇异局势的状态公式:
ak = [ k * (1 + √5) / 2 ] , ([x]表示对x取整,也就是 (int)x )
bk = ak + k
对于hdu1527这个取石子这个题目,也就有了更好的方法。
首先给出的两个数a,b一定要符合上面的公式。
b-a=a+k-a=k;
所以a=[|b-a|*(1 + √5) / 2]
第一步先求出|b-a|=k
第二部k* (1 + √5) / 2 =a0;
第三部比较a0与a是否相等。若相等, 则为必败状态,返回0;不相等, 返回1;
代码如下:(sqrt函数里面写5会显示编译错误,写5.0就对了,可能是函数重载的问题)
#include <stdio.h>
#include<math.h>
#include<stdlib.h>
int main()
{
int a,b;
while(scanf("%d %d",&a,&b)!=EOF)
{
int k=abs(a-b);
double p=(1+sqrt(5.0))/2;
int a0=int(p*k);
if(a>b) a=b;
if(a==a0) printf("0\n");
else printf("1\n");
}
return 0;
}
二、巴什博奕(hdu2188)
选拔规则如下:
1、最初的捐款箱是空的;
2、两人轮流捐款,每次捐款额必须为正整数,并且每人每次捐款最多不超过m元(1<=m<=10)。
3、最先使得总捐款额达到或者超过n元(0<n<10000)的一方为胜者,则其可以亲赴灾区服务。
我们知道,两人都很想入选志愿者名单,并且都是非常聪明的人,假设林队先捐,请你判断谁能入选最后的名单?
2 8 10 11 10
Grass Rabbit
对于这道题我开始的想法其实是分情况讨论,分了三种情况:
1、若n<m,则先手获胜
2、若n-m==1,则后手获胜
3、若n-m>=1,则先手获胜。
开始的思路是没问题的,在n-m~m这个状态内,谁先手谁就获胜。所以是先到n-m(包括n-m),谁就输,因为下一个人一定可以加到m。
后来提交之后显示WA,百度了下巴什博奕,发现第三条情况下,如果n=22,m=10的时候,就不符合条件。
后来发现应该是n%(m+1)==0的时候先手赢,因为先手要想赢,必须最后一个自己来拿(对手足够聪明)。
如果n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s
个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取
法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。
#include <stdio.h>
int main()
{
int x;
int m,n;
scanf("%d",&x);
while(x--)
{
scanf("%d %d",&n,&m);
if(n%(m+1)==0)printf("Rabbit\n");
else printf("Grass\n");
}
return 0;
}
三、找规律博弈(poj1740)
At each step of the game,the player choose a pile,remove at least one stones,then freely move stones from this pile to any other pile that still has stones.
For example:n=4 and the piles have (3,1,4,2) stones.If the player chose the first pile and remove one.Then it can reach the follow states.
2 1 4 2
1 2 4 2(move one stone to Pile 2)
1 1 5 2(move one stone to Pile 3)
1 1 4 3(move one stone to Pile 4)
0 2 5 2(move one stone to Pile 2 and another one to Pile 3)
0 2 4 3(move one stone to Pile 2 and another one to Pile 4)
0 1 5 3(move one stone to Pile 3 and another one to Pile 4)
0 3 4 2(move two stones to Pile 2)
0 1 6 2(move two stones to Pile 3)
0 1 4 4(move two stones to Pile 4)
Alice always moves first. 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.
The last test case is followed by one zero.
3 2 1 3 2 1 1 0
1 0
解题思路(摘自网上):
1、先考虑1堆的时候,1堆当然是N点(必胜点)。
2、然后考虑2堆,细想一下可以发现,当2堆一样时,这个时候的目的就是要把对方给逼到只有2堆都是1的时候,就能必胜了。但是想一下,后手只要模仿先手所做的动作,那么最后就会形成两堆都是1的局势,所以当2堆相同时,是一个P点(必败点)。注意当2堆不一样的时候,先手可以把它变成一样,此时变为N点。
3、考虑3堆,这个时候,先手必定是可以把局势变成2堆相同的堆的,那么先手肯定胜利,为N点。(发现,当堆为偶数堆两两同高的时候,此时是P点)
4、当n >= 4堆的时候可以发现,可以把堆的高度按从小到大排列。
当n为偶数的时候,可以把最高的那一堆跟最小的那一堆变成一样,然后把高度差用来平衡剩余的那些堆,注意一定是可以平衡的,因为把剩余的堆相邻两两的差值投射到y轴上发现这些离散的线段和小于最高堆于最小堆的差值。
当n为奇数的时候,可以把最高堆给去掉,然后分配给其它堆,注意前面的相邻堆两两的差值投射到y轴,最后的总和还是小于最高堆的。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 15
int n;
int a[N];
int main()
{
while (~scanf("%d",&n))
{
if (!n) break;
for (int i=1;i<=n;++i) scanf("%d",&a[i]);
printf("%d %d\n",n,n%2);
if (n%2) {puts("1");continue;}
sort(a+1,a+n+1);
bool flag=true;
for (int i=2;i<=n;i+=2)
if (a[i]!=a[i-1]) {flag=false;break;}
if (flag) puts("0");
else puts("1");
}
return 0;
}