【博弈论】Nim游戏

百度百科

Definition

这样的游戏被称为Nim游戏:
1、有两个玩家,轮流进行操作
2、是公平游戏。即面对同一局面两个玩家所能进行的操作是相同的。例如中国象棋不是公平游戏。因为面对同一个局面,红方只能移动红色棋子而不能移动黑方棋子,黑房同理。
3、一个玩家是输掉当且仅当他无法进行操作。例如如果是两个人轮流取石子的游戏,那么一个玩家输掉当且仅当他面前没有石子了。因为他下面无法进行取石子的操作。
一般的,Nim游戏指两个玩家轮流在好几堆东西中取物品,不能夸堆取,无法操作判负。

Solution

对于Nim游戏的解法,因为状态显然是不可逆的,所以可以对状态的转移图进行拓扑排序然后DP求解。但是对于大部分博弈题目,状态多的难以计算,所以需要考虑更简单的方法。

结论:一个Nim游戏中的状态是必败状态当且仅当每个子游戏的异或和为0.

这里子游戏代表构成Nim游戏中最基础的游戏。例如两个人轮流取三堆石子的游戏是三个取一堆石子的游戏组合而成的。对于每个子游戏显然可以用一个正整数代表当前的状态,即还剩多少石子。Nim游戏的子游戏状态异或和为(0),则必败。
证明:我有一个绝妙的想法,可惜这里写不开
对于一个异或和不为(0)的状态,那么他是必胜态。
考虑在一个必胜态下如何进行下一步。
设异或和是(X)。设(X)二进制的最高为1位是第(k)位。
那么显然存在至少一个状态使得他们二进制第(k)位为1。否则第k位异或和应该是0。
那么就任选一个第k位是1的状态,设他的状态是(Y),那么将他可以将(Y)这一堆取成(Z=X~xor~Y)那么得到的状态是必败状态。

证明如下:

首先,(Y)可以取成(Z)当且仅当(Z~leq~Y)。首先证明(Z~leq~Y)
因为(Y)(X)(k)位都是(1),更高位都是(0)。那么异或后(Z)(k)位一定是(0),更高位显然是(0)。所以(Z)的位数比(Y)的位数要少。那么显然(Z~leq~Y)
下面证明更改后的状态是必败状态
根据(Z=X~xor~Y),并且(A~xor~A=~0~)。可得:
更改后的状态
(S~=~X~xor~Y~xor~Z~=~X~xor~Y~xor~(~X~xor~Y~))
(=~(~X~xor~Y~)~xor~(~X~xor~Y~)~=~0~)
一定是一个必败状态。

Example

传送门

Description

输入k及k个整数n1,n2,…,nk,表示有k堆火柴棒,第i堆火柴棒的根数为ni;接着便是你和计算机取火柴棒的对弈游戏。取的规则如下:每次可以从一堆中取走若干根火柴,也可以一堆全部取走,但不允许跨堆取,也不允许不取。

Input

第一行,一个正整数k
第二行,k个整数n1,n2,…,nk

Output

如果是先取必胜,请在第一行输出两个整数a,b,表示第一次从第b堆取出a个。第二行为第一次取火柴后的状态。如果有多种答案,则输出<b,a>字典序最小的答案(即b最小的前提下a最小)。
如果是先取必败,则输出“lose”。

Sample Input_1

3
3 6 9

Sample Output_1

4 3
3 6 5

Sample Input_2

4
15 22 19 10

Sample Output_2

lose

Hint

(k~leq~500000)
(n_i~leq~1e9)

Solution

板子题要啥solution

Code

#define rg register
#define ci const int
#define cl const long long int

typedef long long int ll;

namespace IO {
	char buf[90];
}

template<typename T>
inline void qr(T &x) {
	char ch=getchar(),lst=' ';
	while(ch>'9'||ch<'0') lst=ch,ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	if(lst=='-') x=-x;
}

template<typename T>
inline void write(T x,const char aft,const bool pt) {
	if(x<0) x=-x,putchar('-');
	int top=0;
	do {
		IO::buf[++top]=x%10+'0';
		x/=10;
	} while(x);
	while(top) putchar(IO::buf[top--]);
	if(pt) putchar(aft);
}

template<typename T>
inline T mmax(const T a,const T b) {if(a>b) return a;return b;}
template<typename T>
inline T mmin(const T a,const T b) {if(a<b) return a;return b;}
template<typename T>
inline T mabs(const T a) {if(a<0) return -a;return a;}

template<typename T>
inline void mswap(T &a,T &b) {
	T temp=a;a=b;b=temp;
}

const int maxn = 500010;

int n,ans;
int MU[maxn];

int main() {
	qr(n);
	for(rg int i=1;i<=n;++i) {
		qr(MU[i]);ans^=MU[i];
	}
	if(!ans) {
		puts("lose");return 0;
	}
	int _temp=1<<30;
	while(!( _temp & ans )) _temp>>=1;
	for(rg int i=1;i<=n;++i) if(MU[i] & _temp){
		int z=ans^MU[i];
		write(MU[i]-z,' ',true);write(i,'
',true);
		for(rg int j=1;j<=n;++j) {
			if(j != i) write(MU[j],' ',true);
			else write(z,' ',true);
		}
		break;
	}
	return 0;
}

Summary

Nim游戏是必败态当且仅当子游戏状态异或和为0。否则是必胜态。通过必胜态一定可以通过一步操作变成必败状态。

原文地址:https://www.cnblogs.com/yifusuyi/p/9572773.html