ZOJ - 3591 NIM

ZOJ - 3591 NIM

  题目大意:给你n,s,w和代码,能生成长度为n的序列,问异或和不为0的子序列有多少个?

  这是个挂羊头卖狗肉的题,和NIM博弈的关系就是要异或和不为0,一开始以博弈甚至循环节的去想这题,完全跑偏了。其实主要还是一个异或和,我们看一组数2 3 2 3 2 3 2,我们像前缀和一样去处理

数           2        3       2       3        2      3       3

异或和    2        1       3       0        2      1       2

位置        1        2       3       4        5      6        7

  我们可以发现位置1到位置5的异或结果是一样的都是2,这能说明什么呢,说明位置1到位置4的异或结果为0,位置4的异或和就是0,这很明显。再来看位置2到位置6的他们异或结果都是1,为什么呢?因为位置2到位置5的异或结果0,中间的异或结果为0,所以从位置2到位置6的异或结果才会相同。我们来看异或和结果一样的位置1,5,7,可以发现它们异或和相等,然后和位置已经没有太大关系,因为异或和相同,说明两个位置中间的异或结果为0。那每两个相同异或和结果的位置就决定了一个为0的子序列。所以我们只需要排个序,然后统计相同异或和的长度len,然后两两组合就是len*(len-1)/2个序列为0,我们只要用总结果n*(n+1)/2减去这个组合的个数就可以得到答案了,还有就是异或和已经等于0的位置就像上面的位置4,他们相当于从位置0(最初)到当前位置的序列,这也要减去。详情见代码。

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 #define ll long long
 5 const int N=101108;
 6 ll n,s,w,a[N],sum[N];
 7 void init()
 8 {
 9     ll g=s;
10     sum[0]=0;
11     for (int i=1;i<=n;i++) 
12     { 
13         a[i]=g;
14         sum[i]=sum[i-1]^a[i];//前缀异或和 
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     sum[n+1]=-1;//为了把最后的也统计上,加上个终止条件 
23 }
24 int main()
25 {
26     int t;
27     scanf("%d",&t);
28     while(t--)
29     {
30         scanf("%lld%lld%lld",&n,&s,&w);
31         init();
32         sort(sum+1,sum+1+n);//把前缀异或和排个序,使相同的在连续区间 
33         ll ans=n*(n+1)/2;
34         for(ll i=1,j=1;i<=n+1;i++)
35         {
36             if(sum[i]==0)//已经是0了减去 
37                 ans--;
38             if(i>1&&sum[i]!=sum[i-1])//减去中间相同区间的组合 
39             {
40                 ans-=(i-j)*(i-j-1)/2;
41                 j=i;
42             }
43         }
44         printf("%lld\n",ans);
45     }
46     return 0;
47 }
前前后后

  这题给了我一个很好的启发,也是在敲板子套模板多了之后给我提了个思考,要变通,要去思考问题。

posted @ 2019-03-13 22:39  新之守护者  阅读(170)  评论(0编辑  收藏  举报