SG函数

36 篇文章 0 订阅
19 篇文章 0 订阅

参考:

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 ,那么当 anssg(i)sg(j)sg(k)=0(i<jk)时,
    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)(1x mod p mod 2)|2px}

考虑 sg(x/p) sg(x/p+1) x/p 的不同取值是 O(x) 的。


证明:

  • p<x 时,显然 x/p 最多只有 x 个取值

  • px 时 , x/pm ,注意到 x/p 是整数,所以最多也只有 x 个取值

做数学题时的常用优化方法。。。


x mod p mod 2 的取值只有两种,枚举一下就行了

另外我们还发现 Tn<=105 , 显然记忆化搜索会比直接打表快很多。


时间复杂度: O(aiai)



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;       
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值