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

本文介绍了一款特殊的回合制游戏,玩家需要通过合理的策略来确保获胜。游戏涉及数学原理及博弈论中的SG函数,通过预处理DP算法计算SG值并采用异或运算确定胜者。

Description

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

Input

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

Output

输出一行,包含 T 个用空格隔开的 001 的数,其中 00 代表此时小 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<100000T<100,N<100,F<100000 ,每堆石子数量 <100000<100000
以上所有数均为正整数。

Solution

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

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

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

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、付费专栏及课程。

余额充值