您的位置:首页 > 其它

UVa 10561 (SG函数 递推) Treblecross

2015-03-10 15:06 435 查看
如果已经有三个相邻的X,则先手已经输了。

如果有两个相邻的X或者两个X相隔一个.,那么先手一定胜。

除去上面两种情况,每个X周围两个格子不能再放X了,因为放完之后,队手下一轮再放一个就输了。

最后当“禁区”布满整行,不能再放X了,那个人就输了。

每放一个X,禁区会把它所在的线段“分割”开来,这若干个片段就可以看做若干个游戏的和。

设g(x)表示x个连续格子对应的SG函数值,递推来求g(x):

g(x) = mex{ g(x-3), g(x-4), g(x-5), g(x-6) xor g(1), g(x-7) xor g(2)... }

g(0) = 0, g(1) = g(2) = g(3) = 1

枚举策略时,就是模拟下一步的状态,记录其中所有必败状态。

#include <cstdio>
#include <cstring>

const int maxn = 200;
char s[maxn + 10];
int g[maxn + 10], ans[maxn + 10];
bool vis[maxn + 10];

bool winning(const char* s)
{
int n = strlen(s);
for(int i = 0; i < n-2; i++)//已经有三个相邻的X,先手输
if(s[i] == 'X' && s[i+1] == 'X' && s[i+2] == 'X') return false;

bool no[n+1];
memset(no, false, sizeof(no));
for(int i = 0; i < n; i++) if(s[i] == 'X')
{
for(int d = -2; d <= 2; d++)
{
if(i+d >= 0 && i+d < n)
{
if(d != 0 && s[i+d] == 'X') return true;//有两个X在彼此的禁区,先手胜
no[i+d] = true;//设置禁区
}
}
}

no
= 1;
int sg = 0;
for(int i = 0; i < n; i++)
{
if(no[i]) continue;
int cnt = 0;
while(i < n && !no[i]) { i++; cnt++; }
sg ^= g[cnt];
}
return sg != 0;
}

int main()
{
//freopen("in.txt", "r", stdin);

g[0] = 0;
g[1] = g[2] = g[3] = 1;
for(int i = 4; i <= maxn; i++)
{//递推求函数g
memset(vis, false, sizeof(vis));
for(int j = 3; i-j >= 0; j++)
{
int v = 0;
v ^= g[i-j];
int x = j - 5;
if(x > 0) v ^= g[x];
vis[v] = true;

for(int j = 0; ; j++) if(!vis[j]) { g[i] = j; break; }
}
}

int T;
scanf("%d", &T);
while(T--)
{
scanf("%s", s);
if(!winning(s)) { printf("LOSING\n\n"); continue; }

puts("WINNING");
int n = strlen(s);
memset(ans, 0, sizeof(ans));
int p = 0;
for(int i = 0; i < n; i++) if(s[i] == '.')
{
s[i] = 'X';
if(!winning(s)) ans[p++] = i+1;//后继必败状态便是先手下一步的策略
s[i] = '.';
}
for(int i = 0; i < p; i++)
{
if(i) printf(" ");
printf("%d", ans[i]);
}
printf("\n");
}

return 0;
}


代码君
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: