BZOJ 1188 分裂游戏 (思路,SG)

题意: 有n个瓶子,之后你每次可以选三个(或两个)瓶子i,j,k(i< j <= k ),将第i个瓶子里取出一颗石子,在第j,k个瓶子里投入一颗石子,最后没有办法操作者输,他现在问你第一步有几种取法,并输出第一步中字典序最小的一个取法。

思路:我们对于任意一堆中的任意一颗石子,那么他的子游戏是往j和k两个瓶子中扔进一个石子,那么对于一堆石子呢就是一颗石子一颗石子的异或和,故,如果一堆石子是偶数个,那么我们就可以不管他,因为他们异或和最后是0,那么奇数堆就是他本身,所以我们现在就可以把游戏抽象成在第i个位置有1个石子,他的后继游戏是可以往j和k个位置放入一颗石子,那么当前位置i的sg值其实就是 sg(j) ^ sg(k) 有了这个表我们就可以得到胜负的关键了,那么我们怎么看先手胜还是先手败呢?我们先把所有的情况异或一遍,之后进行取消操作(也就是 ans = i  ^ j ^ k  , ans ^ i 这样就相当于我们只异或了j和k)如果他是0的话也就是说我先手可以通过这一步将他变成先手必胜态,所以答案++ 就好了

代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 111 + 10;
int a[maxn],SG[maxn];
int n;
int getsg(int x)
{
	if(x == n )return 0;
	if(SG[x] != -1)
	{
		return SG[x];
	}
	int vis[maxn];
	memset(vis,0,sizeof(vis));
	for(int i = x + 1 ; i <= n ; i++)//注意j和k可能会相等 
	{
		for(int j = i ; j <= n ; j++)
		{
			vis[getsg(i) ^ getsg(j)] = 1;
		}
	}
	for(int i = 0 ; ; i ++)
	{
		if(!vis[i])
		{
			return SG[x] = i;
		}
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		memset(a,0,sizeof(a));
		memset(SG,-1,sizeof(SG));
		scanf("%d",&n);
		int ans = 0 ;
		for(int i = 1 ; i <= n ; i++){
			scanf("%d",&a[i]);
			if(a[i] & 1)
			ans = ans ^ getsg(i);
		}
		int cur = 0 ;
		for(int i = 1 ; i <= n ; i++)
		{
			for(int j = i+1; j <= n ; j++)
			{
				for(int k = j ; k <= n ; k ++)
				{
					if(!(ans ^ getsg(i) ^ getsg(j) ^ getsg(k)))//取消i的操作,因为i的sg值其实是他的所有后继游戏的异或和所以这里要异或 j和k 
					{
						if(cur == 0)
						{
							printf("%d %d %d\n",i-1,j-1,k-1);
						}
						cur++;
					}
				}
			}
		}
		if(cur == 0) puts("-1 -1 -1");
		printf("%d\n",cur);
	}
}


猜你喜欢

转载自blog.csdn.net/wjmwsgj/article/details/80203241