第 2 届河北省大学生程序设计竞赛(河北省赛)-Problem B. Nim Game-题解

传送门

Problem A. Mex Query
Problem B. Nim Game
Problem C. icebound 的账单
Problem G. 520
Problem H. 神殿
Problem J. icebound 的商店
Problem K. Bitmap
       哈希讲解
       二维哈希讲解
Problem L. 跑图





Problem B. Nim Game

Time Limit: 2000ms
Memory Limit: 65536KB

Description

Nim is a mathematical game of strategy in which two players take turns removing objects from distinct heaps. On each turn, a player must remove at least one object, and may remove any number of objects provided they all come from the same heap [From Wikipedia, the free encyclopedia]. The goal of the game is to avoid being the player who doesn’t have any
object to remove. The player who remove the last project is the winner.

Now KK and TT are playing Nim game with the optimal strategy. There are n n n heaps of stones. The number of stones in i i i-th heap is a i a_i ai. They play this game m m m times, and KK is the player making the first move. During the i i i-th time they play the game on the heaps whose index in interval [ l i l_i li, r i r_i ri]. KK wants to know whether he has a winning strategy or not

Input

The input consists of several test cases. The first line of the input gives the number of test cases, T ( 1 ≤ T ≤ 1 0 3 ) T(1\leq T\leq 10^3) T(1T103).

For each case, the first line contains two integers n ( 1 ≤ n ≤ 1 0 6 ) n(1\leq n\leq 10^6) n(1n106)and m ( 1 ≤ m ≤ 1 0 6 ) m(1\leq m\leq 10^6) m(1m106), representing the number of heap of stones and the game times.

The second line contains n n n positive integers a 1 , a 2 , ⋯   , a n ( 1 ≤ a i ≤ 1 0 9 ) a_1,a_2,\cdots,a_n(1\leq a_i\leq 10^9) a1,a2,,an(1ai109), representing the number of stones in i i i-th heap.

In the next m m m lines, each line contains two integers l i l_i li, r i r_i ri, which means the i i ith game is played on the interval [ l i l_i li, r i r_i ri].

It’s guaranteed that ∑ n ≤ 2 × 1 0 6 \sum n\leq 2\times 10^6 n2×106

Output

For each test case, let f i f_i fi represents the answer of the i i ith game. If KK has a winning strategy in the i i ith game then f i = 1 f_i= 1 fi=1 , otherwise f i = 0 f_i=0 fi=0 .
Please output ∑ f i ∗ 2 m − i m o d   1 0 9 + 7 \sum f_i*2^{m-i}mod\ 10^9+7 fi2mimod 109+7,in which 1 ≤ i ≤ m 1\leq i \leq m 1im

Sample Input

3
2 1
1 1
1 2
2 1
1 2
1 2
3 2
1 2 2
1 2
2 3

Sample Output

0
1
2

题目大意

每次游戏有一些石头( n n n堆),给你每堆石头的数量,两人轮流开始游戏。
每人可以拿走一堆石头中的任意数量,拿走最后一颗石头的人获胜。
假如两人都足够聪明,问先手赢还是后手赢。


输入描述

第一行一个正整数 T T T,代表一共 T T T组测试样例,对于每组测试样例:

第一行两个正整数 n n n m m m,代表有 n n n堆石头 完 m m m次游戏。
第二行 n n n个正整数,分别代表这 n n n堆石头的石头数量
接下来有 m m m行,对于每行:

有两个正整数 l l l r r r,代表这次游戏的范围是第 l l l~ r r r堆石头。


输出描述

每组测试样例有 m m m次游戏,对于每次游戏:

先手赢记为 1 1 1,后手赢记为 0 0 0

m m m次游戏的加权计算方式为: ∑ f i ∗ 2 m − i m o d   1 0 9 + 7 \sum f_i*2^{m-i}mod\ 10^9+7 fi2mimod 109+7,让你输出这个值。


解题思路

如果你之前 玩过Nim游戏 (做过Nim的题),那么你应该知道,若 n n n堆石子的数量异或结果为 0 0 0,则先手败,否则先手胜。
不知道为什么的可以点击这里参考

知道之后,这一题就变成了前缀了。

每次询问 l l l r r r,每次都从 l l l异或到 r r r肯定超时,因此我们选择使用数组 p r e f i x prefix prefix来辅助。
p r e f i x [ i ] prefix[i] prefix[i]表示前 i i i个数的异或结果为 p r e f i x [ i ] prefix[i] prefix[i]
这样,从 l l l r r r的异或结果为: p r e f i x [ r ] prefix[r] prefix[r] ^ p r e f i x [ l − 1 ] prefix[l-1] prefix[l1]。证明如下:

我们把从 0 0 0 r r r看成两部分,一部分是从 0 0 0 l − 1 l-1 l1,它们的异或结果为 A A A;另一部分是从 l l l r r r,它们的异或结果为 B B B。则 p r e f i x [ l − 1 ] = A , p r e f i x [ r ] = A prefix[l-1]=A,prefix[r]=A prefix[l1]=A,prefix[r]=A ^ B B B
现在已知 A A A ^ B B B A A A来求 B B B,由公式 B = ( A B=(A B=(A ^ B ) B) B) ^ A A A得从 l l l异或到 r r r的结果是 p r e f i x [ r ] prefix[r] prefix[r] ^ p r e f i x [ l − 1 ] prefix[l-1] prefix[l1]

每次游戏可用堆为第 l l l r r r堆,只需要求 p r e f i x [ r ] prefix[r] prefix[r] ^ p r e f i x [ l − 1 ] prefix[l-1] prefix[l1]的结果是否为 0 0 0即可。为 0 0 0则后手胜,记次位为 0 0 0,不为 0 0 0则记次位为 1 1 1
然后至于这个公式“ ∑ f i ∗ 2 m − i m o d   1 0 9 + 7 \sum f_i*2^{m-i}mod\ 10^9+7 fi2mimod 109+7”也不要去怕它。想明白了就是上一个结果左移一位加上这次的结果再取模。

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int prefix[1000010];//记录异或的前缀,prefix[i]表示前i个数的异或结果为prefix[i]
int mod=1e9+7;
int main()
{
    int T;
    cin>>T;//T组测试样例
    prefix[0]=0;//0个数的异或结果记为0
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)//n堆石子
        {
            int t;
            scanf("%d",&t);
            prefix[i]=prefix[i-1]^t;
        }
        ll ans=0;//记得用long long,不然会错
        for(int i=0;i<m;i++)//m次游戏
        {
            int l,r;
            scanf("%d%d",&l,&r);
            int thisScore=(prefix[r]^prefix[l-1])!=0;//如果prefix[r]^prefix[l-1]不等于0,那么先手胜,记1分;否则0分
            ans<<=1;
            ans+=thisScore;
            ans%=mod;
        }
        cout<<ans<<endl;
    }
    return 0;
}





有关Nim游戏的一点解释

其实可以跳过此段👇

一次Nim游戏,我们不妨这样想:
假如初始只有 1 1 1堆,那么先手可以直接拿走全部的石子,先手胜利。 1 ≠ 0 1\neq 0 1=0
但是如果有 2 2 2堆呢?先想最简单的情况:两堆各有 1 1 1个石子。这样的话先手必败,因为他一次只能拿走一堆中的任意个(此情况下只能拿走一堆中的一个),拿走之后就只剩下了一堆,变成了前面的情况,后手拿走全部,先手失败。因此 1 1 1 1 1 1必败。 1 1 1 ^ 1 = 0 1=0 1=0
再升级一下,如果初始状态为 1 1 1 2 2 2呢?那么先手就拿走个数为2的堆中的一个,状态就变成了 1 1 1 1 1 1,后手必败,先手必胜。 1 1 1 ^ 2 = 1 ≠ 0 2=1\neq0 2=1=0
再升级一下,先手2、2必败 ⋯ \cdots 2 2 2 ^ 2 = 0 2=0 2=0
⋯ \cdots

经过数次尝试也好,经过数次猜测也罢,反正最终我们能得到:

n n n堆石子的数量异或结果为 0 0 0,则先手败,否则先手胜。

并且可以证明!不是很好证

要证明这个定理,我们可以证明3个子定理:

  • 当该自己拿时刚好没有石子(0个石子的异或结果记为0),则自己失败。
  • 当该自己拿时所有石子相异或结果不为0时,自己可以一步将其异或结果变为0。(之后该对手拿了,对手面对的是异或结果为0的局面,对手就失败了)
  • 当该自己拿时所有石子异或结果为0时,自己无法使得对手进入异或为0的状态(自己进入异或为0的状态后,无论怎么拿,必将导致异或结果不为0,之后轮到对手,对手面对异或结果不为0的局面,可以一步变回异或结果为0的局面给你,你又陷入了这个异或结果为0的局面)。

通俗地讲,就是谁都想把对手限制在异或结果为0的状态。
假如我能让每次该对手拿时,异或结果都为0,那么随着拿的数量越来越多,剩下的石子越来越少,最终对手会落到没有石子的地步(异或结果仍是0),此刻,对手就输了。


接下来就要证明这3个小定理了:

  • 定理1:规则就是这样,轮到自己拿时没有石子(或者说是对手拿走了最后一颗石子),自己就输了。

  • 定理2:假如现在异或结果不为0,我可以一步使得异或结果变成0:

    假设异或结果为k(≠0),那么一定存在一个a[i],它的 k的二进制下的第一个不为0的位 也不为0。假设这个a[i]^k=t,那么我就把a[i]变成t,这样的话所有的数异或起来就变成了0。(同时可得,一定能从a[i]变成t,因为t=a[i]^k<a[i])

  • 定理3:假如现在异或结果为0,那么无论我拿多少石子(>0),异或结果不可能为0:

    不能拿0颗石子,减小一个非0的数,异或结果要发生变化,不再为0(就无法把对手限制在异或结果为0的局面)。

实在看不懂为什么这样也没太大关系,那就请记住Nim游戏的结论吧,反正他又不会让你去现证 ~_~


原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/116501306

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tisfy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值