传送门:bzoj 1874
题目描述
小H和小Z正在玩一个取石子游戏。 取石子游戏的规则是这样的,每个人每次可以从一堆石子中取出若干个石子,每次取石子的个数有限制,谁不能取石子时就会输掉游戏。 小H先进行操作,他想问你他是否有必胜策略,如果有,第一步如何取石子。
分析
取火柴游戏的升级版,由于数据范围有些水,对此可以考虑直接暴力求SG函数(当然可以用记搜优化).
若每堆的异或和为0,则先手必败;否则必胜,然后再枚举第一次取的石子,若取下石子后每堆的异或和变为0,则输出当前的取石方案.
代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define IL inline
using namespace std;
IL int read()
{
int sum = 0;
bool k = 1;
char c = getchar();
for(;'0' > c || c > '9'; c = getchar())
if(c == '-')
k = 0;
for(;'0' <= c && c <= '9'; c = getchar())
sum = sum * 10 + (c - '0');
return k ? sum : -sum;
}
int n, m;
int num1[15], num2[15];
int sg[1005];
IL int get(int x)
{
if(sg[x] != -1) return sg[x];
bool vis[15];
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= m && x >= num2[i]; ++i)
vis[get(x - num2[i])] = 1;
for(int i = 0; ; ++i)
if(!vis[i])
return sg[x] = i;
}
int main()
{
memset(sg, -1, sizeof(sg));
sg[0] = 0;
n = read(); for(int i = 1; i <= n; ++i) num1[i] = read();
m = read(); for(int i = 1; i <= m; ++i) num2[i] = read();
int ans = 0;
for(int i = 1; i <= n; ++i)
ans ^= get(num1[i]);
if(ans)
{
printf("YES\n");
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m && num2[j] <= num1[i]; ++j)
if(!(sg[num1[i] - num2[j]] ^ (ans ^ sg[num1[i]])))
{
printf("%d %d\n", i, num2[j]);
return 0;
}
}else
printf("NO\n");
return 0;
}