参考:
http://www.cnblogs.com/frog112111/p/3199780.html
http://cyan.logdown.com/posts/286252-combinatorial-games-with-the-sg-function
hdu 1848
bzoj 1188
bzoj 3576
SG SG?SG SG!
hdu 1848
直接求 sg 函数,然后判断游戏和即可。
const int maxn = 16, size = 1000;
int fb[maxn + 5] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987}, sg[size + 5];
void prework()
{
static bool hash[size + 5];
memset(hash, false, sizeof(hash));
for(int i = 1; i <= size; i++)
{
for(int j = 1; j < maxn && fb[j] <= i; j++)
hash[sg[i - fb[j]]] = true;
for(int j = 0; j <= i; j++)
if(!hash[j]) {sg[i] = j; break;}
for(int j = 1; j < maxn && fb[j] <= i; j++)
hash[sg[i - fb[j]]] = false;
}
}
int main()
{
int m, n, q;
#ifndef ONLINE_JUDGE
freopen("1848.in","r",stdin);
freopen("1848.out","w",stdout);
#endif
prework();
read(m), read(n), read(q);
do
{
puts((sg[m]^sg[n]^sg[q]) ? "Fibo" : "Nacci");
read(m), read(n), read(q);
}while(m + n + q);
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
bzoj 1188
考虑在一个位置的瓶子,
按每个瓶子中的巧克力豆个数的奇偶性分类,
巧克力豆个数为奇数时的 sg 函数值都相同,
巧克力豆个数为偶数时时 sg 函数值为 0。
然后求模拟玩法求每个位置巧克力豆个数为奇数时 sg 函数值即可。
如何确定游戏先手能否获胜?
设 ans 为巧克力豆个数为奇数的位置的 sg 函数值的异或和。
如果 ans>0 ,那么 先手有必胜策略。
如何输出第一步的操作?
我们希望后继状态的 sg 值为 0 ,那么当 ans⊕sg(i)⊕sg(j)⊕sg(k)=0(i<j≤k) 时,
对 i , j , k 三个瓶子操作可以保证先手必胜。
const int maxn = 30;
int n, sg[maxn], a[maxn];
int hash[maxn*maxn];
void prework(int size)
{
for(int i = 1; i <= size; i++)
{
for(int j = 1; j < i; j++)
for(int k = 1; k <= j; k++)
hash[sg[j] ^ sg[k]] = i;
for(int j = 0; j <= i*i; j++)
if(hash[j] != i)
{
sg[i] = j;
break;
}
}
}
void init()
{
read(n);
for(int i = 1; i <= n; i++) read(a[i]);
}
void solve()
{
int s1 = 0, s2 = 0, s3 = 0;
int ans = 0, cnt = 0;
for(int i = 1; i <= n; i++) if(a[i]&1) ans ^= sg[n - i + 1];
if(!ans) {puts("-1 -1 -1"), puts("0"); return;}
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
for(int k = j; k <= n; k++)
if(!(ans ^ sg[n - i + 1] ^ sg[n - j + 1] ^ sg[n - k + 1]))
if(!cnt++) s1 = i, s2 = j, s3 = k;
write(s1 - 1), putchar(' ');
write(s2 - 1), putchar(' ');
write(s3 - 1), puts("");
write(cnt), puts("");
}
int main()
{
int T;
#ifndef ONLINE_JUDGE
freopen("1188.in","r",stdin);
freopen("1188.out","w",stdout);
#endif
read(T), prework(maxn - 5);
while(T--) init(), solve();
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
bzoj 3576
这道题稍难一些。
设石子序列为 {ai} , 暴力求 sg 函数 时间复杂度是 O(a2i)
如果把 x 个石子分成 p 堆。
sg(x)=mex{sg(⌊x/p⌋+1)∗(x mod p mod 2)⊕sg(⌊x/p⌋)∗(1−x mod p mod 2)|2≤p≤x}
考虑 sg(⌊x/p⌋) 和 sg(⌊x/p⌋+1) , ⌊x/p⌋ 的不同取值是 O(x√) 的。
证明:
当 p<x√ 时,显然 ⌊x/p⌋ 最多只有 x√ 个取值
当 p≥x√ 时 , x/p≤m ,注意到 ⌊x/p⌋ 是整数,所以最多也只有 x√ 个取值
做数学题时的常用优化方法。。。
而 x mod p mod 2 的取值只有两种,枚举一下就行了
另外我们还发现 T∗n<=105 , 显然记忆化搜索会比直接打表快很多。
时间复杂度: O(ai∗ai−−√)
const int maxs = 1e5 + 50, size = maxs<<1, maxn = 110;
int T, f;
int n, a[maxn];
#define work(t, c) (sg(((t) / (c) + 1)*((t) % (c) &1))^sg((t) / (c)*((c) - (t) % (c) &1)))
int sg(int x)
{
static bool flag[maxs] = {false};
static int hash[size] = {0}, _sg[maxs];
if(x < f) return 0;
if(flag[x]) return _sg[x];
flag[x] = true;
for(int i = 2; i <= x; i = x/(x/i) + 1)
{
hash[work(x, i)] = x;
if(i < x) hash[work(x, i + 1)] = x;
}
int pos = 0;
while(hash[pos] == x) pos++;
return (_sg[x] = pos);
}
int solve()
{
int ans = 0;
read(n);
for(int i = 1; i <= n; i++)
read(a[i]), ans ^= sg(a[i]);
return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("3576.in","r",stdin);
freopen("3576.out","w",stdout);
#endif
read(T), read(f);
while(T--) putchar('0' + (solve()?1:0)), putchar(T?' ':'\n');
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}