[SDOI2009]E&D 题解

[SDOI2009]E&D 题解

题目地址:洛谷:【P2148】[SDOI2009]E&D – 洛谷、BZOJ:Problem 1228. — [SDOI2009]E&D

题目描述

小E 与小W 进行一项名为“E&D”游戏。
游戏的规则如下: 桌子上有2n 堆石子,编号为1..2n。其中,为了方便起见,我们将第2k-1 堆与第2k 堆 (1 ≤ k ≤ n)视为同一组。第i堆的石子个数用一个正整数Si表示。 一次分割操作指的是,从桌子上任取一堆石子,将其移走。然后分割它同一组的另一堆 石子,从中取出若干个石子放在被移走的位置,组成新的一堆。操作完成后,所有堆的石子 数必须保证大于0。显然,被分割的一堆的石子数至少要为2。 两个人轮流进行分割操作。如果轮到某人进行操作时,所有堆的石子数均为1,则此时 没有石子可以操作,判此人输掉比赛。
小E 进行第一次分割。他想知道,是否存在某种策 略使得他一定能战胜小W。因此,他求助于小F,也就是你,请你告诉他是否存在必胜策略。 例如,假设初始时桌子上有4 堆石子,数量分别为1,2,3,1。小E可以选择移走第1堆, 然后将第2堆分割(只能分出1 个石子)。接下来,小W 只能选择移走第4 堆,然后将第3 堆分割为1 和2。最后轮到小E,他只能移走后两堆中数量为1 的一堆,将另一堆分割为1 和1。这样,轮到小W 时,所有堆的数量均为1,则他输掉了比赛。故小E 存在必胜策略。

题意简述

有一个游戏,给偶数堆石子,两个相邻石子为一组,游戏的每次操作要选出一组,将其中一堆石子移走并将另外一堆分成两堆作为新的一组石子。当一方无法进行操作的时候即输,求是否存在先手必胜策略。

输入输出格式

输入格式:
第一行是一个正整数T(T ≤ 20),表示测试数据数量。接下来有T组 数据。 对于每组数据,第一行是一个正整数N,表示桌子上共有N堆石子。其中,输入数据保 证N是偶数。 第二行有N个正整数S1..SN,分别表示每一堆的石子数。

输出格式:
包含T 行。对于每组数据,如果小E 必胜,则输出一行”YES”,否则 输出”NO”。

输入输出样例

输入样例#1:

2
4
1 2 3 1
6
1 1 1 1 1 1

输出样例#1:

YES
NO

说明

【数据规模和约定】
对于20%的数据,N = 2;
对于另外20%的数据,N ≤ 4,Si ≤ 50;
对于100%的数据,N ≤ 2×10^4 ,Si ≤ 2×10^9 。

题解

参考资料:bzoj1228 [SDOI2009]E&D(博弈【规律) – CSDN博客
这里可以将一组石子视为一个独立的Nim游戏,根据SG定理,所有组的SG值异或和非0即为先手必胜,反之为先手必败。
一组石子的SG如何来求,我们当然可以采用爆搜,但是时间不允许。然后我们可以打个表,设SG(x, y)为一组的状态为(x, y)时的SG值,发现SG值有以下规律:
SG(x, y) = \begin{cases} 0, & x, y都是奇数 \\ SG(\lceil \frac{x}{2} \rceil, \lceil \frac{y}{2} \rceil) + 1, & x, y至少有一个是偶数 \end{cases}
于是现在这个搜索的复杂度就变成了O(\log n)的了,现在我们有了一个O(n \log n)的优秀做法。
关于这张表长什么样子,可以看参考资料那位同学的博文。

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

int T, n, x, y;

inline int sg(int x, int y) {
    if((x & 1) && (y & 1)) return 0;
    else if(!(x & 1) && !(y & 1)) return sg(x >> 1, y >> 1) + 1;
    else if(x & 1) return sg((x + 1) >> 1, y >> 1) + 1;
    else return sg(x >> 1, (y + 1) >> 1) + 1;
}

int main() {
    T = readint();
    while(T--) {
        int res = 0;
        n = readint() >> 1;
        for(int i = 1; i <= n; i++) {
            x = readint(); y = readint();
            res ^= sg(x, y);
        }
        if(res) puts("YES"); else puts("NO");
    }
    return 0;
}


发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据