[BZOJ3576][Hnoi2014]江南乐(SG函数+博弈dp+数论)

Description

小 A 是一个名副其实的狂热的回合制游戏玩家。在获得了许多回合制游戏的世界级奖项之后,小 A 有一天突然想起了他小时候在江南玩过的一个回合制游戏。
游戏的规则是这样的,首先给定一个数 F F ,然后游戏系统会产生 T 组游戏。每一组游戏包含 N N 堆石子,小 A 和他的对手轮流操作。每次操作时,操作者先选定一个不小于 2 2 的正整数 M M M 是操作者自行选定的,而且每次操作时可不一样),然后将任意一堆数量不小于 F 的石子分成 M M 堆,并且满足这 M 堆石子中石子数最多的一堆至多比石子数最少的一堆多 1 1 (即分的尽量平均,事实上按照这样的分石子万法,选定 M 和一堆石子后,它分出来的状态是固定的)。当一个玩家不能操作的时候,也就是当每一堆石子的数量都严格小于 F F 时,他就输掉。(补充:先手从 N 堆石子中选择一堆数量不小于 F F 的石子分成 M 堆后,此时共有 N+M1 N + M − 1 堆石子,接下来小 A 从这 N+M1 N + M − 1 堆石子中选择一堆数量不小于 F F 的石子,依此类推)
小 A 从小就是个有风度的男生,他邀请他的对手作为先手。小 A 现在想要知道,面对给定的一组游戏,而且他的对手也和他一样聪明绝顶的话,究竟谁能够获得胜利?

Input

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

Output

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

Sample Input

4 3
1 1
1 2
1 3
1 5

Sample Output

0 0 1 1

HINT

对于 100% 的数据, T<100,N<100,F<100000 T < 100 , N < 100 , F < 100000 ,每堆石子数量 <100000 < 100000 <script type="math/tex" id="MathJax-Element-626"><100000</script> 。
以上所有数均为正整数。

Solution

发现初始的 N N 堆石子分别都对应了一个独立的游戏,因此只需要把这 N 堆分开,计算它们的 SG S G 值后异或起来即可。
在读入 T T 组游戏之前先进行一个预处理 DP :
SG[i] 表示当前还剩下 i i 个石子的 SG 值。
边界:当 0i<F 0 ≤ i < F 时, SG[i]=0 S G [ i ] = 0
转移:

SG[i]=mexij=2{W(SG[ij+1],imodj) XOR W(SG[ij],jimodj)} S G [ i ] = mex j = 2 i { W ( S G [ ⌊ i j ⌋ + 1 ] , i mod j )  XOR  W ( S G [ ⌊ i j ⌋ ] , j − i mod j ) }

其中 W(x,y) W ( x , y ) 的定义为:如果 y y 是奇数则 W(x,y)=x ,否则 W(x,y)=0 W ( x , y ) = 0
暴力转移是平方的,显然 TLE 。
但是可以发现 ij ⌊ i j ⌋ 的取值只有 i i 种,可以考虑枚举 ij ⌊ i j ⌋ 的取值来计算。
假设现在枚举到了 ij=k ⌊ i j ⌋ = k ,那么这时候 SG[k] S G [ k ] SG[k+1] S G [ k + 1 ] 的值就能快速得到了。但我们还需要知道 imodj i mod j jimodj j − i mod j 的奇偶性。
j j 为奇数和偶数两种情况处理,这样知道了 imodj 的奇偶性也就知道了 jimodj j − i mod j 的奇偶性。
设当 j[l,r] j ∈ [ l , r ] 时, ij=k ⌊ i j ⌋ = k
j j 为偶数(需判断 [l,r] 内是否有偶数)时比较显然,如果 i i 为偶数那么 imodj 也为偶数,否则为奇数。
j j 为奇数(需判断 [l,r] 内是否有奇数)时,如果 i×k i − 任 意 奇 数 × k 得到偶数那么 imodj i mod j 也为偶数,否则为奇数。
于是,就可以在根号的复杂度内完成一次转移了。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, Z = 87;
int n, T, F, sg[N], tmp[Z];
void orzcyxdalao() {
    int i, j; For (i, F, 100000) {
        memset(tmp, 0, sizeof(tmp)); for (j = 2; j <= i;) {
            int nxt = i / (i / j), p1 = sg[i / j], p2 = sg[i / j + 1], k = i / j;
            if (!(j & 1) || j < nxt) tmp[i & 1 ? p1 ^ p2 : 0] = 1;
            if ((j & 1) || j < nxt)
                tmp[i - (j & 1 ? j : j + 1) * k & 1 ? p2 : p1] = 1;
            j = nxt + 1;
        }
        For (j, 0, 2147483647) if (!tmp[j]) {sg[i] = j; break;}
    }
}
int main() {
    int i; T = read(); F = read(); orzcyxdalao();
    while (T--) {
        int ans = 0; n = read(); For (i, 1, n) ans ^= sg[read()];
        printf(ans ? "1 " : "0 ");
    }
    cout << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值