思想很奇妙,代码很简短的过程
只有一堆n个石子,两个人轮流从这堆石子中子,规定每次至少取一个,最多取m个.最后取光者得胜.
若n%(m+1)=0,则先手必败,否则先手必胜。若n=(m+1)×r+s 则先手先拿走那个s,然后从必败转回必胜
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1846
PN分析法
http://acm.hdu.edu.cn/showproblem.php?pid=2147
这个规律怎么找呢?从行和列方向都是奇偶奇偶 必胜必败交错,行和列都是基数那就是必败,都是偶数那就是必胜,单独分析一下基数乘偶数是必胜,可以想到乘积为偶数为必胜
https://www.cnblogs.com/henry-1202/p/9744678.html
参考这篇博客,讲的特别好
例题:
http://acm.hdu.edu.cn/showproblem.php?pid=2516
奇异局势 https://blog.csdn.net/qq_41311604/article/details/79980882
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1527
betty定理:用来推“奇异局势”的一个公式
有3堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取1个,多者不限,最后取光者得胜。
首先自己想一下,就会发现只要最后剩两堆物品一样多(不为零),第三堆为零,那面对这种局势的一方就必败
那我们用(a,b,c)表示某种局势,首先(0,0,0)显然是必败态,无论谁面对(0,0,0) ,都必然失败;第二种必败态是(0,n,n),自己在某一堆拿走k(k ≤ n)个物品,不论k为多少,对方只要在另一堆拿走k个物品,最后自己都将面临(0,0,0)的局势,必败。仔细分析一下,(1,2,3)也是必败态,无论自己如何拿,接下来对手都可以把局势变为(0,n,n)的情形
那这种奇异局势有什么特点呢?
亦或 相同为0,不同为1
任何奇异局势都有a 亦或b 亦或c为0
把不是奇异局势转化成奇异局势 ,(1,2,4)不是奇异局势,那么就把4变成1^2 这样1亦或2 然后再 亦或(1亦或2) 答案一定是0,因为亦或 ,相同为0,不同为1,所以4如何变成1亦或2呢 当然是4-(4-1亦或2)了,或者,亦或是一个很神奇的东西,把4变成3可以由4亦或(k) ,k为(1亦或2亦或4的结果),这个可以推出来,要把4变成(1亦或2) 可以减,也可以直接添上一个亦或4就OK
想详细知道为什么用亦或表示的参考: https://www.cnblogs.com/jiangjun/archive/2012/11/01/2749937.html
例题;http://acm.hdu.edu.cn/showproblem.php?pid=1850
阶梯博弈:
阶梯的序号如图所示,地面表示第0号阶梯。每次都可以将一个阶梯上的石子向其左侧移动任意个石子,没有可以移动的空间时(及所有石子都位于地面时)输。
策略如下:https://blog.csdn.net/qq_30241305/article/details/51956518
一道阶梯尼姆变形的题目:http://poj.org/problem?id=1704
就是把他捆绑,代码也挺简单的
https://blog.csdn.net/h_666666/article/details/86683679这个是一开始我的代码,是分奇偶写的,其实不用哎,可以直接这么减
#include
#include
#include
#define maxn 1010
using namespace std;
int main()
{
int t,a[maxn];
scanf("%d",&t);
while(t--)
{
int n,sum=0;
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
}
a[0]=0;
sort(a+1,a+n+1);//要先排序,注意地址不要弄错。
for(int i=n; i>0; i=i-2)
{
sum^=(a[i]-a[i-1]-1);
}
if(sum)printf("Georgia will win\n");
else printf("Bob will win\n");
}
return 0;
}
(一个例题:)比较难判断
https://ac.nowcoder.com/acm/contest/897/E
题解:
阶梯博弈变型
如果有奇数堆石子,就在最前面添加一个空堆使其变成偶数堆。所以只需讨论偶数堆的情况。然后再将每相邻的两堆合并成一组。对于合并后的每一组,每一组的sg值为
(x2−x1)%(k+1)。最后把每一组的sg值异或起来,不为0则Bob获胜,否则Alice获胜。
假设必败者移除的是奇数项的堆,那么必胜者只需移除该项后面一项同样数目即可。假设必败者移除的是偶数项的堆,那么问题就转变成了多堆石子博弈问题。
#include
#include
#include
#include
#include
using namespace std;
int a[10010];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
int ans=0,num=0;
a[0]=0;
//if(k!=2)
// a[0]=-1;
for(int i=n;i>0;i-=2)
{
num=(a[i]-a[i-1])%(k+1);
ans^=num;
}
if(ans!=0)
printf("Bob\n");
else
printf("Alice\n");
}
return 0;
}
5、SG函数 其实这个才是做博弈题的王道呀
SG值:一个点的Sg值就是一个不等于他的后继点的Sg的且大于等于0的最小整数,大概意思就是:在步骤允许的情况下,与前一个必败点的差(也就是说这个差是规定的,能走的,其中一个步数)
参考: https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG©,那么SG(x) = mex{SG(a),SG(b),SG©}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
【实例】取石子问题
有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?
SG[0]=0,f[]={1,3,4},
x=1 时,可以取走 f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;
x=2 时,可以取走 f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;
x=3 时,可以取走 f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;
x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;
x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;
以此类推…
x 0 1 2 3 4 5 6 7 8…
SG[x] 0 1 0 1 2 3 2 0 1…
由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:
1、使用 数组f 将 可改变当前状态 的方式记录下来。
2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。
3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。
4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。
例题1:http://acm.hdu.edu.cn/showproblem.php?pid=1847
例题2:http://acm.hdu.edu.cn/showproblem.php?pid=1848
例题二代码
import java.util.Scanner;
class Main{
public static void main(String[] args) {
while (true){
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
int n = sc.nextInt();
int p = sc.nextInt();
if(m==0 && n==0 && p==0) {
break;
}
initf();
// for(int i = 0;i<20;i++){
// System.out.println(f[i]+ " ");
// }
getsg(m,n,p);
System.out.println(sg[m]+" "+sg[n]+" "+sg[p]);
if((sg[m] ^sg[n]^sg[p])==0) System.out.println("Nacci");
else System.out.println("Fibo");
}
}
static int max=1005;
static int f[] = new int[max+5];
static void initf(){
f[0] = 1;
f[1] = 2;
f[2] = 3;
for(int i = 3;i<max;i++){
f[i] = f[i-1]+f[i-2];
}
}
static int sg[] = new int[max];
static int s[] =new int[max]; //为后既点的集合
static void getsg(int m,int n,int p){
sg[0] = 0;
int i;int j;
sg = new int[max];
for( i = 1;i<=n;i++){//每一堆从1到max都打一次表
s = new int[max];
for(j =0;(f[j]<=i&& j<=max);j++){
s[sg[i-f[j]]] = 1;//当前后既点进行标记
}
for(int x = 0;;x++){
if(s[x]==0){
sg[i] =x;
break;
}
}
}
}
}