HNOI 2014 Day2 江南乐

第 3 题:江南乐(game),运行时限 3s,内存上限 512M


【问题描述】

小 A 是一个名副其实的狂热的回合制游戏玩家。在获得了许多回合制游戏的世界级奖项

之后,小 A 有一天突然想起了他小时候在江南玩过的一个回合制游戏。

游戏的规则是这样的,首先给定一个数 F,然后游戏系统会产生 T 组游戏。每一组游戏包

含 N 堆石子,小 A 和他的对手轮流操作。每次操作时,操作者先选定一个不小于 2 的正整数

M(M 是操作者自行选定的,而且每次操作时可不一样),然后将任意一堆数量不小于 F 的石

子分成 M 堆,并且满足这 M 堆石子中石子数最多的一堆 至多 比石子数最少的一堆多 1(即

分的尽量平均,事实上按照这样的分石子方法,选定 M 和一堆石子后,它分出来的状态是固

定的)。当一个玩家不能操作的时候,也就是当每一堆石子的数量都严格小于 F 时,他就输掉

了。(补充:先手从 N 堆石子中选择一堆数量不小于 F 的石子分成 M 堆后,此时共有 N+M-1

堆石子,接下来小 A 从这 N+M-1 堆石子中选择一堆数量不小于 F 的石子,依此类推。)

小 A 从小就是个有风度的男生,他邀请他的对手作为先手。小 A 现在想要知道,面对给

定的一组游戏,而且他的对手也和他一样聪明绝顶的话,究竟谁能够获得胜利?


【输入格式】

输入文件为 game.in。

输入第一行包含两个正整数 T 和 F,分别表示游戏组数与给定的数。

接下来 T 行,每行第一个数 N 表示该组游戏初始状态下有多少堆石子。之后 N 个正整数,

表示这 N 堆石子分别有多少个。


【输出格式】

输出文件为 game.out。

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

1 代表小 A 的对手(先手)会胜利。


【样例输入】

4 3

1 1

1 2

1 3

1 5


【样例输出】

0 0 1 1


【数据范围】

对于 10%的数据,T=1,N=1,F≤1,每堆石子数量≤10;

对于 20%的数据,T≤100,N≤2,F≤1,每堆石子数量≤10;

对于 30%的数据,T≤100,N≤100,F≤1,每堆石子数量≤10;

对于 40%的数据,T≤100,N≤100,F≤5,每堆石子数量≤15;

对于 70%的数据,T≤100,N≤100,F≤1000,每堆石子数量≤1000;

对于 100%的数据,T≤100,N≤100,F≤100000,每堆石子数量≤100000。

以上所有数均为正整数。


【本人题解】

对于10分的数据:明显当石子数大于1时,先手胜。因为只有一堆,先手只要把石子全拆成只有1个的若干堆就赢了,但如果本来就只有1个石子,就是后手赢,复杂度O(1)。

对于70分的数据:直接暴力求在当前的 f 值下每个石堆对的的sg值即可。每堆石子有O(n)中分裂方式,每个分裂方式的sg求值是O(1)的,有O(n)个石子,复杂度O(n ^ 2)。

对于100分的数据:我们考虑两个性质

1、(n / i)(商向下取整),至多有 2 * sqrt(n) 个不同的结果。

2、如果 n / i == n / (i + 2) ,那么通过将石子分裂成 i 份得到的 i 堆石子的sg值异或和与将石子分裂成 i + 2 份石子所得到的结果相同。

如果上述两个性质是对的,我们就可以只枚举 n / i 来求解,因为有很多分裂方式在本质上是相同的,我们完全可以不考虑。

现在我们来证明这些性质:

证明性质1:在1 - sqrt(n) 之间,因为只有 sqrt(n) 个数,所以 (n / i) 在1 - sqrt(n) 之间当然最多只有sqrt(n) 个结果。

                              在 sqrt(n) - n 之间,因为 n / n = 1 ,  n / sqrt(n) = sqrt(n),n 除以该范围内的数的结果必然在1 - sqrt(n) 之间 。所以 (n / i) 在 sqrt(n) - n 之间也只会出现sqrt(n) 个不同的结果。

                              综上,在1 - n之间,(n / i) 至多有 2 * sqrt(n) 个不同的结果,性质 1 是正确的。

证明性质2:因为把 n 个石子按照题目要求分成 i 份,会产生 n % i 个石子数为 n / i + 1 的石头堆,i - n % i 个石子数为 n / i 的石头堆。(这个挺显然的)

                              而把 n 个石子按照题目要求分成 i + 1 份,就相当于在分成了 i 份的基础上,从 n / i  个石子数为 n / i + 1 的石头堆中,各拿出1个石子,组成一个新的石子数为n / i 的石头堆。

      而把 n 个石子按题目要求分成 i + 2 份的话,就相当于做了两次组新石子堆的工作,其石子堆的奇偶性不变,因为你操作了两次,所以各种石子堆的改变量就必然是2的倍数,既然这样,石子堆的sg值异或和就不会变。

      所以性质 2 是正确的。

好的,现在我们保证了这个算法的正确性,来说说具体做法吧。

对于每个石子数n,我们枚举它的 n / i 值,然后取分裂成 n / i 份和 n / (i +1) 份的两种情况就可以了。

实现过程中,我们还可以进行记忆化搜索来提高程序的效率。

每个石子数的sg求值是 O(sqrt(n)) 的,有 O(n) 个石子,所以时间复杂度是 O(n * sqrt(n)) 的。


【本人代码】

#include
#include
#include
#include
#include
using namespace std;
#define N 100000 + 5
int T, F, id, mex[N], sg[N];
bool done[N];

int Get_sg(int n)
{
    if (n < F)
        return 0;
    if (done[n])
        return sg[n];
    done[n] = 1;
    for (int i = 2; i <= n; i = n / (n / i) + 1)
        for (int j = i; j <= i + 1 && j <= n; j ++)
            Get_sg(n / j), Get_sg(n / j + 1);
    id ++;
    for (int i = 2; i <= n; i = n / (n / i) + 1)
        for (int j = i; j <= i + 1 && j <= n; j ++)
        {
            int tmp = 0;
            if ((n % j) % 2 == 1) tmp ^= sg[n / j + 1];
            if ((j - n % j) % 2 == 1) tmp ^= sg[n / j];
            mex[tmp] = id;
        }
    for (sg[n] = 0; mex[sg[n]] == id; sg[n] ++) ;
    return sg[n];
}

void begin()
{
    freopen("game.in", "r", stdin);
    freopen("game.out", "w", stdout);
    scanf("%d%d", &T, &F);
}

void work()
{
    int t, xsum = 0;
    scanf("%d", &t);
    while (t --)
    {
        int x;
        scanf("%d", &x);
        xsum ^= Get_sg(x);
    }
    printf("%d ", xsum == 0 ? 0 : 1);
}

void end()
{
    fclose(stdin);
    fclose(stdout);
}

int main()
{
    begin();
    while (T --)
        work();
    end();
    return 0;
}


你可能感兴趣的:(OI,HNOI,2014,Day2,江南乐,game)