威佐夫博奕

转: https://blog.csdn.net/y990041769/article/details/21694007

威佐夫博弈是博弈中的另一个经典模型。

问题:首先有两堆石子,博弈双方每次可以取一堆石子中的任意个,不能不取,或者取两堆石子中的相同个。先取完者赢。

分析:首先我们根据条件来分析博弈中的奇异局势

      第一个(0 , 0),先手输,当游戏某一方面对( 0 , 0)时,他没有办法取了,那么肯定是先手在上一局取完了,那么输。

第二个 ( 1  , 2  ),先手输,先手只有四种取法,

1)取 1 中的一个,那么后手取第二堆中两个。

2)取 2 中一个,那么后手在两堆中各取一个。

3)在 2 中取两个,那么后手在第一堆中取一个。

4)两堆中各取一个,那么后手在第二堆中取一个。

可以看出,不论先手怎么取,后说总是能赢。所以先手必输!

第三个 ( 3 , 5 ),先手必输。首先先手必定不能把任意一堆取完,如果取完了很明显后手取完另一堆先手必输,那么

假如看取一堆的情况,假设先手先在第一堆中取。 取 1 个,后手第二堆中取4个,变成(1 ,2)了,上面分析了是先手的必输局。

 取 2 个,后手第二堆中取3个,也变成( 1 , 2)局面了。

假设先手在第二堆中取,取 1 个,那么后手在两堆中各取 2 个,也变成 ( 1 , 2 )局面了。

   取 2 个 ,那么后手可以两堆中都去三个, 变成 ( 0 , 0)局面,上面分析其必输。

   取  3  个,后手两堆各取 1 个 ,变成( 1 , 2)局面了。

  取 4 个,后手在第一堆中取一个,变成( 1 , 2)局面了。

可见不论先手怎么取,其必输!

第四个(4  , 7),先手必输。

自己推理可以发现不论第一次先手如何取,那么后手总是会变成前面分析过的先手的必输局面。

那么到底有什么规律没有呢,我们继续往下写。

第四个 ( 6 ,10  )

第五个 ( 8 ,13)

第六个 ( 9 , 15)

第七个 ( 11 ,18)

会发现他们的差值是递增的,为 0 , 1 , 2, 3, 4 , 5 , 6, 7.....n

而用数学方法分析发现局面中第一个值为前面局面中没有出现过的第一个值,比如第三个局面,前面出现了 0  1 2,那么第三个局面的第一个值为 3 ,比如第五个局面,前

面出现了 0  1  2 3 4 5 ,那么第五个局面第一个值为6。

再找规律的话我们会发现,第一个值 = 差值 * 1.618 

而1.618 = (sqrt(5)+ 1) /  2 。

大家都知道0.618是黄金分割率。而威佐夫博弈正好是1.618,这就是博弈的奇妙之处!

下面来看看威佐夫博弈常见的三类问题:

1)给你一个局面,让你求是先手输赢。

有了上面的分析,那么这个问题应该不难解决。首先求出差值,差值 * 1.618 == 最小值 的话后手赢,否则先手赢。(注意这里的1.618最好是用上面式子计算出来的,否则精

度要求高的题目会错)

例如:nyoj 161

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
int main(){
	int a,b;//a>=b
	while(scanf("%d %d",&a,&b)!=EOF){
		if(a<b)
		swap(a,b);
		if(floor((a-b)*((sqrt(5.0)+1.0)/2.0))==b)
		printf("0\n");
		else
		printf("1\n");
	}
	return 0;
}

2)给你一个局面,让你求先手输赢,假设先手赢的话输出他第一次的取法。

       首先讨论在两边同时取的情况,很明显两边同时取的话,不论怎样取他的差值是不会变的,那么我们可以根据差值计算出其中的小的值,然后加上差值就是大的一个值,当

然能取的条件是求出的最小的值不能大于其中小的一堆的石子数目。

      加入在一堆中取的话,可以取任意一堆,那么其差值也是不定的,但是我们可以枚举差值,差值范围是0 --- 大的石子数目,然后根据上面的理论判断满足条件的话就是一种合理的取法。

例如:nyoj 886

#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
int main(){
	int a,b,t;
	//判断容易,要输出的有点复杂 
	double p=((sqrt(5.0))+1.0)/2;//威佐夫博奕公式(该值正好是黄金比例数) 
	while(~scanf("%d%d",&a,&b),a,b)//a,b表示的是两堆中剩余的石子个数 
	{
		if(a>b)
		swap(a,b);
		int s=b-a;
	    int k=floor(s*p);
	    if(k==a)//判断是否是必败局面 
	        printf("0\n");
	    else
	    {
	    	printf("1\n");
	    	if(a-k==b-(k+s)&&a>=k)//从两堆中拿走相同数量的石子  注意两个条件都要写,不然WA 
	    	    printf("%d %d\n",k,k+s);
	    	for(int i=0; ;i++)//从一堆中拿 
	    	{
	    		int temp=i*p;
	    		if(temp>=b)
	    		    break;
	    		if(b>temp+i&&a==temp)//从b中拿石子,且拿完b比a大 
	    		    printf("%d %d\n",temp,temp+i);
	    		else if(a==temp+i)//从b中拿石子,但拿完b比a小
	    		    printf("%d %d\n",temp,temp+i);
	    		else if(b==temp+i&&a>temp)//从a中拿,拿完肯定a还是小于b 
	    		    printf("%d %d\n",temp,temp+i);
	    	}
	    }
	}
} 	

3)直接求前n个必败态(奇异局势)

比如:nyoj 837

#include<stdio.h>
#include<math.h>
int a[100005][2];//存储前n个必败局的坐标 
int main(){
	int n;
	for(int i=1;i<=10000;i++){
		a[i][0]=i*(sqrt(5.0)+1)/2;
		a[i][1]=a[i][0]+i;
	}
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<=n;i++)
		printf("(%d,%d)",a[i][0],a[i][1]);
		printf("\n");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/dear_jia/article/details/80199852
今日推荐