一、必胜点与必败点
规则:当一方不能将游戏继续下去时,游戏结束,对方获胜。
必败点(P点) 前一个(previous player)选手将取胜的点称为必败点
必胜点(N点) 下一个(next player)选手将取胜的点称为必胜点
(1) 所有终结点是必败点(P点);
(2) 从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点);
(3)无论如何操作, 从必败点(P点)都只能进入必胜点(N点).
方法:
步骤1:将所有终结位置标记为必败点(P点);
步骤2: 将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4: 如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2。
hdu2147
画出PN图,即可得结论。
二、Nim博弈
有若干堆石子,分别有N1,N2...Nm颗,2人轮流取子,每次可以任选一堆,取任意颗。最后无法再操作的人输。
解法:将所有石子数异或,N1 ^ N2 ^ ... Nm,若为0,则先手必败;不为0,则先手必胜。
hdu1850 Being a Good Boy in Spring Festival
求先手获胜,有多少种走法。
#include <iostream>
#include <cstdio>
using namespacestd;
const int maxn =100 +5;
int num[maxn];
int main()
{
int m,cnt,ans =0;
while (scanf("%d",&m) !=EOF && m) {
ans = 0;
for (int i =0; i < m; i ++) {
scanf("%d",&num[i]);
ans ^= num[i];
}
cnt = 0;
if(ans){
for (int i =0; i < m; i ++) {
if (num[i] >= (ans ^ num[i])) {//若可以通过将第i堆,取走一些,使剩下的堆异或为0,则cnt++
cnt ++;
}
}
}
printf("%d\n",cnt);
}
return0;
}
三、SG函数
将若干堆石子,看作一个状态,起始状态处有一颗棋子,则玩家的每次操作,就是将棋子沿有向无回图的边移动,移动至无法移动时,则游戏结束。
然后为每个状态x(即有向无回图的每个顶点),定义一个SG函数值,SG(x) = mex{ x所有后继y的SG(y) },mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。
必败点P SG(P) = 0;
必胜点N SG(N) > 0;
若SG(x) = k > 0,则说明,状态x的后继的sg值为0,1,...k - 1,移动x,sg值可能变为0,1,..,k - 1(与Nim博弈类似,一堆k颗石子,取任意颗后,可能是0,1,..k - 1颗)
所以,有Nim博弈可以得到,如果有m颗棋子,位于m个有向无回图中,每次可以选任意一颗棋子移动,棋子sg值分别为SG(x1),SG(x2)...SG(xm),若异或值为0,则先手必败。
如何计算SG函数
1.若可以取任意颗(Nim博弈),则SG(x) = x
(所以Nim博弈可以直接将石子数异或,不用求SG)
2.每次可以取1-m颗,则SG(x) = x % (1 + m)
3.给定k种操作,每次可以s1,s2...sk颗
//k 操作数,s[]记录每种操作可以取多少颗
void cal_sg()//预处理所有sg(x) O(maxk * maxn)
{
memset(g, 0, sizeof(g));//SG函数
for (int i = 1; i < maxn; i ++) {
memset(mp, 0, sizeof(mp));//记录所有后继的sg值,求mex
for (int j = 0; j < k; j ++) {
if(i < s[j]) break;
mp[g[i - s[j]]] = 1;
}
for (int j = 0; j < maxn; j ++) {
if(mp[j] == 0) {g[i] = j;break;}
}
}
}
int get_sg(int x)//计算单独sg(x)递归求
{
memset(g, -1, sizeof(g));
mp[0] = 0;
int i;
for (i = 0; i < k; i ++) {
if(x < s[i]) break;
if(g[x - s[i]] == -1) g[x - s[i]] = get_sg(x - s[i]);
mp[g[x - s[i]]] = 1;
}
for(i = 0;;i ++) {if(mp[i] == 0) return g[x] = i;}
}
2人3堆石子m,n,p,每次可取fibonacci数列中的个数(1,2,3,5,8....)
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int maxn =1000 +5;
vector<int> fibo;
int g[maxn];
int mp[maxn];
void get_fibo()
{
fibo.push_back(1),fibo.push_back(2);
for (int i =2;; i ++) {
fibo.push_back(fibo[i - 1] + fibo[i -2]);
if(fibo[i] > maxn)break;
}
}
void cal_sg()//预处理计算maxn内的sg值 O(maxn * k)
{
memset(g, 0,sizeof(g));
for (int i =1; i < maxn; i ++) {
memset(mp, 0,sizeof(mp));
for (int j =0; j < fibo.size(); j ++) {
if(i < fibo[j])break;
mp[g[i - fibo[j]]] = 1;
}
for (int j =0;; j ++) {
if(mp[j] ==0) {g[i] = j;break;}
}
}
}
int main()
{
int m,n,p;
get_fibo();
cal_sg();
while (scanf("%d%d%d",&m,&n,&p) != EOF) {
if(m ==0 && n ==0 && p ==0)break;
if (g[m] ^ g[n] ^ g[p]) printf("Fibo\n");
else printf("Nacci\n");
}
return0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespacestd;
const int maxk =100 +5;
const int maxn =1e4 +5;
int s[maxk];
int g[maxn];
int k;
int get_sg(int x)//计算单独sg(x)递归求
{
if(g[x] != -1)returng[x];
int mp[maxk] = {0};//!!!貌似在全局定义,每次memset会超时?
int i;
for (i =0; i <k; i ++) {
if(x <s[i])break;
if(g[x -s[i]] == -1)g[x - s[i]] =get_sg(x -s[i]);
mp[g[x -s[i]]] =1;
}
for(i =0;;i ++) {if(mp[i] ==0)returng[x] = i;}
}
int main()
{
int m,l,t,ans;
while (scanf("%d",&k) != EOF && k) {
memset(s,0,sizeof(s));
memset(g, -1,sizeof(g));
g[0] =0;
for (int i =0; i <k; i ++) {
scanf("%d",&s[i]);
}
sort(s,s +k);
scanf("%d",&m);
for (int i =0; i < m; i ++) {
scanf("%d",&l);
ans = 0;
for (int j =0; j < l; j ++) {
scanf("%d",&t);
ans ^= get_sg(t);
}
if(ans)printf("W");
elseprintf("L");
}
printf("\n");
}
return0;
}