[博弈]ZOJ3591 Nim

题意:

给了一串数,个数不超过$10^5$,这串数是通过题目给的一段代码来生成的

int g = S; 
         
 for (int i=0; i<N; i++) { 
         

              a[i] = g;
         

              if( a[i] == 0 ) { a[i] = g = W; }
         

              if( g%2 == 0 ) { g = (g/2); }
         

              else           { g = (g/2) ^ W; }
         

          }

其中S、N、W都是输入的。

问:从中取连续的一段出来玩Nim博弈,先手赢的取法有多少种。

 

Nim博弈的结论:每堆异或,最后结果为0的先手输,否则,先手赢;

于是这道题就变成了取连续的一段,异或值不为0的取法数。

 

N最大有1e5,显然需要O(n)复杂度的方法

异或没什么感觉...如果这道题问的是 取连续的一段,和为X的取法有多少种 

那么就很自然的能想到前缀和  第i到第j的总和为sum[j]-sum[i-1]

那联想到这道题,能不能求个前缀异或呢? 

对!

而且异或能分为 0 与 非0 两种情况

那么在查询 第i到第j 这一段的异或值时,只需要比较 xor_sum[i] 与 xor_sum[j]

假设xor_sum[i]=X; xor_sum[j]=X;

那么很显然i+1到j的这一段为0  ( X xor 0 = X)

我们只需要记录有多少段为0,用总的n*(n+1)/2去减去就是答案了;

 

为什么要记录有多少段为0?记录有多少段为0?

我们已经有前缀异或,那么当第二次出现某一值时,这个值 前一次出现的位置 到 本次出现的位置 之间这一段 就是异或值为0的。

因此我们统计异或值为0的要方便的多。

做法就是记录所有的xor_sum出现的次数加起来就好了嘛

 

 1 int a[100005];
 2 map<LL, LL> mp;
 3 int main()
 4 {
 5     int t;
 6     scanf("%d", &t);
 7     while(t--)
 8     {
 9         int n, s, w;
10         scanf("%d%d%d", &n, &s, &w);
11         int g = s;
12         for (int i=0; i<n; i++)
13         {
14             a[i] = g;
15             if( a[i] == 0 )
16                 a[i] = g = w;
17             if( g%2 == 0 )
18                 g = (g/2);
19             else
20                 g = (g/2) ^ w; 
21         }
22         LL xor_sum=0, ans=0;
23         mp.clear();
24         mp[0]=1;
25         for(int i=0;i<n;i++)
26         {
27             xor_sum^=a[i];
28             ans+=mp[xor_sum];
29             mp[xor_sum]++;
30         }
31         print((LL)n*(n+1)/2-ans);
32     }
33     return 0;
34 }
ZOJ 3591

 

posted @ 2015-03-31 21:30  Empress  阅读(259)  评论(0编辑  收藏  举报