2023第14届蓝桥杯大赛软件赛省赛C/C++大学A组第8题题解:异或和之和

目录

问题描述:

方法一:暴力枚举(50%)

方法二:前缀和优化(90%)

方法三:前缀和+按位分解+乘法原理


问题描述:

        给定一个数组 Ai,分别求其每个子段的异或和,并求出它们的和。或者说, 对于每组满足 1 L R n L, R ,求出数组中第 L 至第 R 个元素的异或和。然后输出每组 L, R 得到的结果加起来的值。

输入格式:

        输入的第一行包含一个整数 n

        第二行包含 n 个整数 Ai ,相邻整数之间使用一个空格分隔。

输出格式:

        输出一行包含一个整数表示答案。

评测用例规模与约定:

        对于 30% 的评测用例,n ≤ 300 ;

        对于 60% 的评测用例,n ≤ 5000 ;

        对于所有评测用例,1 ≤ n 10^50 Ai 2^20

方法一:暴力枚举(50%)

        直接按题目的描述写即可,必须开long long才能得分。

#include 
using namespace std;
long long a[200005];
int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
    cin>>a[i];
  long long ans=0;//必须开long long!!
  for(int i=1;i<=n;i++)//枚举l
  {
    for(int j=i;j<=n;j++)//枚举r
    {
      long long sum=0;
      for(int k=i;k<=j;k++)
      {
        sum^=a[k];//从l到r的异或
      }
      ans+=sum;//异或和相加
    }
  }
  cout<

方法二:前缀和优化(90%)

        类比加法的前缀和预处理出表示数组前n项的异或和的数组sum,那么从l到r的异或和就是sum[r]^sum[l-1]。因为在这个式子中,前l-1项被异或了两次,而x^x=0,x^0=x,所以前l-1项均被异或两次后全都等于0,0对其他数字的异或不产生影响,sum[r]^sum[l-1]也就等于其中只异或了一次的l到r的异或和。

#include 
using namespace std;
long long a[200005];
long long sum[200005];
int main() {
  int n;
  cin >> n;
  for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
  for(int i=1;i<=n;i++)
    sum[i]=sum[i-1]^a[i];//异或和的前缀和
  long long ans = 0;
  for(int i=1;i<=n;i++)
  {
    for(int j=i;j<=n;j++)
    {
      ans+=sum[j]^sum[i-1];//i到j的异或和
    }
  }
  cout << ans;
  return 0;
}

方法三:前缀和+按位分解+乘法原理

        本题的数据范围ai<=2^20,那么每一对异或和都不会超过2^20也就是异或和的二进制最多有20位。这个异或和就等于二进制的每一位数字(0或1)乘以2的k(位数)次幂再相加。那么上面的代码就可以改成:

#include 
using namespace std;
long long a[200005];
long long sum[200005];
int main() {
  int n;
  cin >> n;
  for(int i=1;i<=n;i++)
    cin>>a[i];
  for(int i=1;i<=n;i++)
    sum[i]=sum[i-1]^a[i];
  long long ans = 0;
  for(int k=0;k<=20;k++)//枚举每一对异或和的二进制第k位
  {
    for(int i=1;i<=n;i++)
    {
      for(int j=i;j<=n;j++)
      {
        long long temp=((sum[j]^sum[i-1])>>k)&1;//位运算取1个数字的第k位数字
        ans+=temp*(1<

        这么做有什么用呢?不就是把十进制数转成二进制又转化回来了吗,程序多循环了20遍,不是化简为繁吗?没错,上面的代码只能得到70%的分数,真的是反向优化。

        但是观察上面的代码,其中的temp也就是某一对异或和的二进制第k位,只有0和1两个取值,且只有当temp为1时,ans才会增加。而要使temp为1,sum[j]和sum[i-1]的第k位必须一个为0一个为1。

        也就是说,对于所有异或和的第k位乘以2^k的总和,等于所有第k位为1的异或和的个数乘以2^k。所有的异或和都是通过sum[l-1]异或sum[r]得到的,第k位为1的异或和的总数,就等于所有l和r中,sum[l-1]和sum[r]的第k位只有一个1的总对数。设sum[n]中有x个第k为0,y个第k位为1,只有一个1的l,r总对数就等于x*y,(根据乘法原理)。

        遍历一遍前缀和sum,分别统计出第k位0和1的个数,相乘就得到了第k位为1的异或和的个数,乘以2^k就可以加入ans。

#include
using namespace std;
long long a[100005],sum[100005];
int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];
    sum[i]=sum[i-1]^a[i];//前缀和
  }
  long long cnt[2],ans=0;
  for(int k=0;k<=20;k++)
  {
    cnt[0]=0;//0的个数
    cnt[1]=0;//1的个数
    for(int i=0;i<=n;i++)
    {
      int t=(sum[i]>>k)&1;//取sum[i]的第k位
      cnt[t]++;//对应数目+1
    }
    ans+=(1<

        本题的重点是一定要开long long!

        本题的重点是一定要开long long!

        本题的重点是一定要开long long!

你可能感兴趣的:(蓝桥杯,c++,算法)