2017.3.24 分裂游戏 思考记录

        事先说明:这不是好题解,这只是思考记录


         同机房的xp都学博弈了、、    感觉还是学学看吧


         先来看一下简单的nim:

题目:

    Alice和Bob放置了N堆不同的石子,编号1..N,第i堆中有A[i]个石子。
每一次行动,Alice和Bob可以选择从一堆石子中取出任意数量的石子。至少取1颗,至多取出这一堆剩下的所有石子。
Alice和Bob轮流行动,取走最后一个石子的人获得胜利。
假设每一轮游戏都是Alice先行动,请你判断在给定的情况下,如果双方都足够聪明,谁会获得胜利?


 题目太长了!画个图压压惊:



如果你有博弈恐惧症,那你可以看一下比题目还短的代码来提振信心:

#include <bits/stdc++.h>
 using namespace std;
 int n, p, a;
 int main() {
     while (cin >> n) {
         for (int i = 0; i < n; ++i) {
             cin >> a;
             if (i == 0) p = a;
             else p ^= a;
         }
         if (p == 0) cout << "Bob" << endl;
         else cout << "Alice" << endl;
     }
     return 0;
 }


好了,开始学习预备知识:

首先认识一下公平组合游戏(ICG)

它的规则是:

1、有两名玩家(p1、p2)

2、每一个玩家可以动 游戏里的每个棋子(没有红方、黑方的棋子之分、 、“动”指和原来的局面不一样)

3、不许作弊、、没有buff、、没人开黑

4、动不了的选手输(所以游戏就是比谁拿走最后一个、、)


只有两个玩家非常机智才能叫博弈

当然,如果两个ZZ玩游戏,那就叫菜鸡互啄、、

所以两个玩家都做出最优的决定;;(你知道我的套路,我知道你的套路,如果我进入你的套路我就输了)

所以先手有主动权,可以一步使对方进入自己的套路

而有时,游戏一开始的局面就是对方的套路,那就相当于对方先把你套路了,然后你就没办法了、、


所以对于这类游戏,胜负取决于初始局面、、

研究这种局面的胜负 就叫狭义的 博弈论、、


所以局面分两类:

P-position:在当前的局面下,先手必败。
N-position:在当前的局面下,先手必胜。

他们有如下性质:
1.合法操作集合为空的局面是P-position;
2.可以移动到P-position的局面是N-position;
3.所有移动都只能到N-position的局面是P-position。


对于1:动不了的肯定输

对于2:这里的先手是相对的  这一回合你是先手,下一回合他是先手,如果你冰雪聪明是可以把下一个先手(对方,也就是这一个后手)套路的

       如果你不聪明,那你玩游戏就不能叫博弈

对于3:无论如何下一个先手都赢了,你就只能gg了


 根据性质3是可以递推的、、但一般写记搜+递归

  

所以,我们将p写作0,n写作1

所以  0前面都是1

        1后面一定有0 、、囧

就像这个:


如果

对于上图的路径特点我们可以想到什么?

就是二进制的 异或^

但为什么要用每一堆的石子数 异或?



证明(来自百度百科):

第一个命题显然,terminal position只有一个,就是全0, 异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为 异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。

我个人感觉这应该只是异或的性质和nim游戏的博弈法则不谋而合

所以等量代换了、、



sg函数:

它是博弈的核心;;;

它存的是每个状态(石子数);;


先来看几个简单的知识:

游戏的和:

如果每次游戏可以分成n个子游戏,每次只能操作其中1个子游戏:

f(总)=f(第一个子游戏)^f(第二个子游戏)^f(第三个子游戏)…………^f(第n个子游戏)

结合nim很好证明


下面几个图帮助理解:


由于每个子游戏相同,堆中数相同时输赢相同,可以简化为:



注意是两个棋子在起点

这就可以递归判断当前局面


有了这些知识和模型,平等博弈类的问题已经可以解答了

对于这个题:

我们先划分子游戏,以便合为这个分裂游戏:


每个堆的转移是会互相影响的(++--)

但是每个石子的转移相互独立(一个石子的转移不会影响另一个石子的转移)

所以每个石子都是一个游戏、、

在每一堆里,每个石子都有不同的sg值

一颗在i被取走的石子会在j、k加入一颗石子

我们注意到只有最后一个有石子的情况是P

也就是说所有的石子随着时间推移都会被扔到最后一个

所以当枚举堆数到了最后一堆时,就是terminal position(末端情况)

所以取走当前的石子游戏可以看成是往右的j、k位置的方案

另外我们注意到石子的具体个数是没有关系的,只与奇偶性有关(所有>1的偶数都早晚要被互拿转移为0,所以就当它是0,所有>1的奇数都会被拿为1,即交换了一次先后手,所以会对答案造成影响)



所以需要计算玩i这个石子(虽然一点都不好玩),也一定要玩到j、k  一直玩到n,,就一路异或即可

所以玩这个石子的情况=初始局面 -i的1  +j的1   +k的1、、


由于你可以玩石子数为奇数的堆数颗石子的取一+2游戏

所以总游戏就是  取这些堆的子游戏异或和^sg(i)^sg(j)^sg(k)(sg是一大串异或,是初始局面     由于异或的性质,+和-都可以用^表示)


码:

#include<iostream>
#include<cstdio>
using namespace std;
#include<cstring>
int n,j,i,k,f[30],T,x,ans,cnt;
bool  vis[40]; 
int getsg(int x)
{//cout<<x;
int j;
	if(x==n)return 0;//cout<<f[x]<<endl;
	if(f[x]!=-1)return f[x];
	memset(vis,0,sizeof(vis));
	for(int j=x+1;j<=n;j++)
	for(int k=j;k<=n;k++)
	{
		vis[getsg(j)^getsg(k)]=1;
	//	cout<<getsg(j)<<" "<<getsg(k)<<endl;	
	}
	for(j=0;vis[j];j++);//不要脑残的写:  for(int j;vis[j],j++); 

	return f[x]=j;
	
}




int main()
{
	scanf("%d",&T);

	while(T--)
	{
		ans=cnt=0;	memset(f,-1,sizeof(f));//一开始没清0 
		scanf("%d",&n);
		for(i=1;i<=n;i++)
		{
			scanf("%d",&x);
			if(x&1)ans^=getsg(i);//统计初始局面 
		//	cout<<ans<<endl;		
		}
		for(i=1;i<n;i++)
		for(j=i+1;j<=n;j++)
		for(k=j;k<=n;k++)
		{
			if(!(ans^getsg(i)^getsg(j)^getsg(k)))if((++cnt)==1)printf("%d %d %d\n",i-1,j-1,k-1);
		}
		if (!cnt) puts("-1 -1 -1"); printf("%d\n",cnt);  //输出-1 -1 -1 隔开      输出方案总数  
    }  
 } 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值