• 【bzoj3576】[Hnoi2014]江南乐 博弈论+SG定理+数学


    题目描述

    两人进行 $T$ 轮游戏,给定参数 $F$ ,每轮给出 $N$ 堆石子,先手和后手轮流选择石子数大于等于 $F$ 的一堆,将其分成任意(大于1)堆,使得这些堆中石子数最多的和最少的相差不超过1(即尽量均分)。求先手和后手谁必胜。

    输入

    输入第一行包含两个正整数T和F,分别表示游戏组数与给定的数。
    接下来T行,每行第一个数N表示该组游戏初始状态下有多少堆石子。之后N个正整数,表示这N堆石子分别有多少个。

    输出

    输出一行,包含T个用空格隔开的0或1的数,其中0代表此时小A(后手)会胜利,而1代表小A的对手(先手)会胜利。

    样例输入

    4 3 
    1 1 
    1 2 
    1 3 
    1 5 

    样例输出

    0 0 1 1 


    题解

    博弈论+SG定理+数学

    显然游戏为多状态组合游戏,考虑使用SG定理求解。

    SG定理:定义每种局面的SG函数值为该局面所有子局面SG函数值的mex,多状态组合游戏的SG值为每个状态SG值的异或。SG值为0时先手必败,否则先手必胜。

    设 $f[i]$ 表示一堆 $i$ 个石子的SG函数值,当 $i<f$ 时 $f[i]=0$ 。枚举分成的堆数 $j$ ,根据定义有 $f[i]= ext{mex}_{j=2}^{i}f[frac ij] ext{^}f[frac ij] ext{^}... ext{^}f[frac ij] ext{^}f[frac ij+1] ext{^}f[frac ij+1] ext{^}... ext{^}f[frac ij+1]$ 。
    其中 $ ext{^}$ 表示异或,$f[frac ij]$ 有 $j-i\%j$ 个,$f[frac ij+1]$ 有 $i\%j$ 个,$\%$ 表示模。

    这里出现了 $frac ij$ ,可以使用数学下底分块的方法枚举商,求出 $frac ij$ 相等的一段区间。

    由于 $x ext{^}x=0$ ,因此只需要考虑 $j-i\%j$ 和 $i\%j$ 的奇偶性即可。

    又因为 $i\%j=i-j·frac ij$ ,因此当 $frac ij$ 为定值时,相同奇偶性的 $j$ 对应的答案相同。

    具体来讲 ,$ ext{mex}$ 后面的部分当 $j$ 为偶数时:$i$ 为奇数时为 $f[frac ij] ext{^}f[frac ij+1]$ ,否则为 $0$ ;当 $j$ 为奇数时:$i-j$ 为奇数为 $f[frac ij+1]$ ,否则为 $f[frac ij]$ 。

    之后暴力求 $ ext{mex}$ 即可(复杂度是对的)。

    最后对于询问,将每堆的SG值异或即可得到最终的SG值。

    时间复杂度 $O(nsqrt n)$ 。

    bzoj上比较卡常,需要使用时间戳优化来记录每个SG是否作为子状态出现。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    int f[100010] , v[100010];
    inline void calc(int n , int c)
    {
        int i , t , last;
        for(i = 2 ; i <= n ; i = last + 1)
        {
            t = n / i , last = n / t;
            if(last / 2 > (i - 1) / 2)
            {
                if(n & 1) v[f[t] ^ f[t + 1]] = c;
                else v[0] = c;
            }
            if((last + 1) / 2 > i / 2)
            {
                if((n - t) & 1) v[f[t + 1]] = c;
                else v[f[t]] = c;
            }
        }
    }
    int main()
    {
        int T , c , i , n , x , val;
        scanf("%d%d" , &T , &c);
        for(i = c ; i < 100000 ; i ++ )
        {
            calc(i , i);
            while(v[f[i]] == i) f[i] ++ ;
        }
        while(T -- )
        {
            scanf("%d" , &n) , val = 0;
            for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &x) , val ^= f[x];
            printf("%d" , (bool)val);
            if(T) printf(" ");
        }
        return 0;
    }
    

     

  • 相关阅读:
    与平面和空间打交道的计算几何
    借助水流解决问题的网络流
    计算几何算法概览
    关于while (~scanf("%d %d", &m, &n))的用法
    Minimizing maximizer(POJ 1769)
    java九九乘法表
    java替换字符串中的World为Money
    java截取字符串,第4位以后的字符串用*代替
    java使用valueOf的方法反转字符串输出
    java使用StringBuilder的方法反转字符串输出
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8612430.html
Copyright © 2020-2023  润新知