题意: 两个人一起玩扫雷,都不点炸弹,谁先不能点谁就输。问谁赢。
解法: 在题目保证不出现重叠的情况下,就可以转化为类似取石子的Nim游戏(取1个或者取整堆)。其他部分看题解的,这里略过。
第一次做博弈题,大概了解了一下这类题的思路。下面描述一下简单Nim博弈的解法。
对于n堆石子,它的子游戏是一堆石子,通过分析一堆石子的SG值,将所有堆对应的SG值异或,得到的结果非0为先手赢,0为后手赢。
这类题如果纯推演的话,是很复杂的,所以要借助SG定理。(SG定理的证明这里略过,实在是被博弈惨虐,不求甚解,知道怎么用就可以了ORZ)。
SG值的定义:SG值是对于一个状态,他的所有后继状态的SG值之外的最小非负整数。
对于必败状态,它的SG值为0。必胜状态的SG值为非0。
Nim游戏的每一个状态的值,都是确定的,不是必胜就是必败。
必胜状态:存在至少一个后继为必败状态。
必败状态:所有的后继状态均为必胜状态或者没有后继状态。
所以每一个非末状态的SG值都是可以通过后继状态推知,末状态的SG值通常为0。
然后就是寻找SG值的规律,推出结论,秒题。
注意在抽象出取石子模型之后的一些特殊细节。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define clr(a,b) memset(a,b,sizeof(a))
int dir[8][2] = {1,0,0,1,-1,0,0,-1,1,1,1,-1,-1,1,-1,-1};
int g[1100][1100];
int n,m,k;
struct Q {
int x, y;
}q[1001000],f,e;
int bfs(int x, int y) {
int qf,qe;
int ret = 0;
qf = qe = 0;
q[0].x = x;
q[0].y = y;
g[x][y] = -2;
while(qf <= qe) {
f = q[qf++];
for(int i=0; i<8; i++) {
int dx = f.x + dir[i][0];
int dy = f.y + dir[i][1];
if(dx<0 || dx>=n || dy<0 || dy>=m || g[dx][dy] < 0) continue;
if(g[dx][dy] > 0) {
ret ++;
}
else {
q[++qe].x = dx;
q[qe].y = dy;
}
g[dx][dy] = -2;
}
}
return ret;
}
int main() {
int cas, ca = 0;
scanf("%d", &cas);
int x,y;
while(cas--) {
scanf("%d%d%d", &n, &m, &k);
clr(g,0);
while(k--) {
scanf("%d%d", &x, &y);
g[x][y] = -1;
for(int i=0; i<8; i++) {
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if(dx<0 || dx>=n || dy<0 ||dy>=m || g[dx][dy] < 0) continue;
g[dx][dy] = 1;
}
}
int ans = 0;
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
if(g[i][j] == 0) ans ^= bfs(i,j)%2+1;
}
}
int tot = 0;
for(int i=0; i<n; i++) {
for(int j=0; j<m; j++) {
if(g[i][j] > 0) tot++;
}
}
ans ^= tot&1;
if(ans) printf("Case #%d: Xiemao\n",++ca);
else printf("Case #%d: Fanglaoshi\n",++ca);
}
return 0;
}