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 ",ans); 45 } 46 return 0; 47 }
这题给了我一个很好的启发,也是在敲板子套模板多了之后给我提了个思考,要变通,要去思考问题。