uva10561:Treblecross(SG函数 + 分情况讨论)

题目大意:有两个人轮流在一个 1 * n的序列上选择一个空的格子放X,若某个人放下X时出现三个连续X,这该人获胜。输入的序列中可能已经包含一些X,保证不包含连续的3个X。
你的任务是判断先手必胜还是先手必败,若先手必胜,输出所有必胜策略的第一步放X的位置。

分析:
若序列中出现形如 XX,或X.X这样的连续的序列,则先手必胜,且必须放在XX的两边或X.X的中间。
若序列中没有这种情况:先看一段连续的空白的序列,当放下一个X时,双方都采取最优策略,则X的旁边已经X的旁边的旁边不会去放X,因为如果放了X,则下一步对方一定能凑出连续的三个X(自己画一段看看)。X以及它半径的范围构成一个禁区不能放X,这样一段连续的序列会被分割成两段,两段分别是两个独立的游戏,因此可以用SG函数。令连续区间的长度为状态,边界状态sg函数值为:sg[0] = 0,sg[1] = sg[2] = sg[3] = 1。其他状态可以枚举 ‘X’ 所在的分割点递推求得:
sg[x] = mes{sg[x - 3],sg[x -4],sg[x - 5],sg[1] ^ sg[x - 6],sg[2] ^ sg[x - 7] …}。

对于求必胜策略:若有XX 或X.X这样的序列,最优解就是那是放在哪些使得出现连续的3个X的位置。对于没有XX和X.X这样的序列,题目中已包括的X会将原序列分割成多段连续的区间。枚举 所有的决策位置点,求一下该位置放X后的sg函数值,必胜决策的位置就是那些放了X后sg函数值为0的位置,注意X不能放在禁区(最优决策不会这样做,这样放直接走向了必胜态,先手输,而且SG函数求的是不在禁区放X的SG值)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e2 + 10;
int t;
char str[maxn];
int sg[maxn];
int vis[maxn * 100];
vector<int> ans;
int getsg() {
	int v = 0,last = 0;
	int i;
	for(i = 1; str[i]; i++) {
		if(str[i] == 'X') {
			if(!last) {
				v ^= sg[i - 3];
			}
			else {
				int p = i - last - 5;
				if(p >= 0)
					v ^= sg[p];
			}
			last = i;
		}
	}	
	if(last) {
		int p = i - last - 3;
		if(p > 0)
			v ^= sg[p];
	}
	else {
		v ^= sg[i - 1];
	}
	return v;
}
void solve() {
	int i;
	for(i = 1; str[i + 1]; i++) {
		if(str[i] == 'X' && str[i + 1] == 'X') {
			if(i > 1) ans.push_back(i - 1);
			if(str[i + 2]) ans.push_back(i + 2);
		}
		if(str[i] == 'X' && str[i + 2] == 'X') {
			ans.push_back(i + 1);
		}
	}	
}
int main() {
	scanf("%d",&t);
	sg[0] = 0;
	sg[1] = sg[2] = sg[3] = 1;
	for(int i = 4; i <= 300; i++) {
		memset(vis,0,sizeof(vis));
		for(int j = 1; j <= i; j++) {
			if(j - 2 > 0)
				vis[sg[j - 3] ^ sg[i - j - 2 > 0 ? i - j - 2 : 0]] = 1;
			else
				vis[sg[i - j - 2]] = 1;
		}
		for(int j = 0; ; j++) {
			if(!vis[j]) {
				sg[i] = j;
				break;
			}
		}		
	}
	while(t--) {
		memset(str,0,sizeof(str));
		scanf("%s",str + 1);
		ans.clear();
		solve();
		if(!ans.size()) {
			for(int i = 1; str[i]; i++) {
				if(str[i] != 'X') {
					str[i] = 'X';
					if(str[i - 1] != 'X' && str[i - 2] != 'X' && str[i + 1] != 'X' && str[i + 2] != 'X') {
						if(getsg() == 0) ans.push_back(i);
					}
					str[i] = '.';
				}
			}
		}
		if(!ans.size()) {
			puts("LOSING\n");
		}
		else {
			puts("WINNING");
			for(int i = 0; i < ans.size(); i++) {
				if(i) printf(" ");
				printf("%d",ans[i]);
			}
			cout << endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值