序列子区间问题一般都是问你,求序列子区间的和的和...异或和的和..和的异或和...和是3的倍数的个数...
这类问题一般都是用前缀和+记录状态的数组来实现,将O(n*n)的复杂度降为O(k*n)(k为常数)
西安电子科技大学第16届程序设计竞赛 E Xieldy And His Password
题意:给你一个串,只由0,1组成,问你该串的子串表示的二进制数是3的倍数的个数(0也是3的倍数,且允许有前导0)
解析:
如果一个二进制数某一位是1,那么它表示2^k,那么2^k%3一定=1 or 2
并且一个二进制数,奇数位(从0开始)上的1一定%3=2,偶数位(从0开始)上的1%3=1
那么我们就用cnt[]记录当前时刻,前面前缀和%3=0,1,2的相应的个数(不包括当前时刻),
那么我们在遍历到i时,只要看当前i的前缀和%3的余数tt,看cnt[]数组里面有多少个前缀区间值为tt的,
那么相应减去这些前缀区间,就是以i为结尾的满足条件的区间的个数
#include <iostream> #include<string> #include<bits/stdc++.h> #include<cstdio> using namespace std; string a; #define N 1000006 int num[N]; int sum[N]; int cnt[4]; #define ll long long int main() { ios::sync_with_stdio(false); while(cin>>a){ for(int i = 0;i<a.length();i++){ //在任意一个2进制位上1,表示的2^k%3=1 or 2 if(a[i]=='0') num[i+1] = 0; else{ if(i%2) num[i+1] = 2; else num[1+i] = 1; } } memset(cnt,0,sizeof(cnt)); ll ans = 0; for(int i = 0;i<a.length();i++){ int n = num[i+1]; sum[i+1] = (sum[i]+n)%3; int tt = sum[i+1]; ans += cnt[tt]; //当前前缀和%3为tt,所以前面只要有前缀和%3为tt,就刚好减掉这个前缀区间,就符合条件 if(sum[i+1]==0) //所以只要加上前面是tt的前缀区间的个数,就可以了,并且这里一定能保证求出来的区间是连续的子区间(以i结尾) ans++; //同时i的前缀区间满足条件的话,还要加上去,因为只是不需要减的情况 cnt[tt]++; //这里其实就是用cnt[]这个数组优化了遍历(以i结尾)子区间的过程,将O(n)的复杂度降为O(1) } cout<<ans<<endl; } return 0; }
题意:
求所有子区间的异或和的和
解析:
这里需要思考的时如何用状态数组来优化遍历到一个点,往前查找答案的过程
因为从值的本身来找状态,无从下手,这里是异或,那么我们就可以从它的二进制来下手
因为是子区间问题,我们要求前缀和,这样才能保证我们的区间是连续的
当遍历到一个前缀和a[i]时,我们需要把以i为结尾的区间的答案全部找出来
这里我们就可以用到二进制了,当a[i]的k位为1时,我们答案要加上2^k*zero[k],zero[k]表示前面前缀区间值对应第k位为0的个数,因为只有与这些前缀区间异或后,
结果中第k位才不为0,这样才能将2^k这个值加到答案里(a[i](k)为1的时候同理)
例如,i>j,sum[j]为j的前缀异或和,sum[i]同理,sum[i](k)表示sum[i]的二进制第k位
当sum[i](k)去遍历前面的前缀区间时,遍历到sum[j]时,如果sum[i](k)=1,sum[j](k)=0,那么[j+1,i]这个区间的异或和的第k位一定是1,这样就可以往答案加上2^k
如果sum[i](k)=0,sum[j](k)=1,那么这个区间的异或和的第k位一定是1,同样可以往答案加上2^k
#include<bits/stdc++.h> #define fi first #define se second #define mp make_pair #define pb push_back #define pi acos(-1.0) #define ll long long #define mod 20090717 #define C 0.5772156649 #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 #define pii pair<int,int> using namespace std; const double g=10.0,eps=1e-12; const int N=200000+10,maxn=200000+10,inf=0x3f3f3f3f; ll a[N]; ll one[50],zero[50]; int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; for(int i=1;i<=n;i++)cin>>a[i],a[i]^=a[i-1]; ll sum=0; for(int i=0;i<=n;i++) // { for(int j=0;j<30;j++) { if(((a[i]>>j)&1))//1 { sum+=zero[j]*(1ll<<j); //表示前缀和a[i]的第j位1于前面的所有前缀和异或之后,有几个1(即前面对应位子0的个数) one[j]++; } else { sum+=one[j]*(1ll<<j); zero[j]++; } } } cout<<sum<<endl; return 0; } /******************** 1 2 ********************/