序列子区间问题

序列子区间问题一般都是问你,求序列子区间的和的和...异或和的和..和的异或和...和是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;
}

U14739 X ask Y III 子区间异或和

题意:

求所有子区间的异或和的和

解析:

这里需要思考的时如何用状态数组来优化遍历到一个点,往前查找答案的过程

因为从值的本身来找状态,无从下手,这里是异或,那么我们就可以从它的二进制来下手

因为是子区间问题,我们要求前缀和,这样才能保证我们的区间是连续的

当遍历到一个前缀和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
********************/





猜你喜欢

转载自blog.csdn.net/qq_37025443/article/details/80062826