数论——异或,两道水题。

Posted zzozz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数论——异或,两道水题。相关的知识,希望对你有一定的参考价值。

第一题:(没有链接)

题目大意:给你n个数(n <= 1000000),第i个数用ai表示(每个a <= 1000000),求出每个数与其之后的数的xor和。

举个例子吧,比如三个数1 2 3答案就应该为1 xor 2 + 1 xor 3 + 2 xor 3 = 4;

题解:

首先有一个O(n*n)的算法,就是暴力枚举,可以过40%数据。

程序大体:

for (int i = 1;i < n;i ++)
    for (int j = i + 1;j <= n;j ++) ans += a[i] ^ a[j];

很明显TLE,考虑一下两个数的二进制的某一位xor能产生贡献的条件是什么?就是两位不同。。。 也就是其中一个是1,另一个是0二进制下的这一位就可以产生贡献。咦?好像有很大的用。那么我们可以很容易的想到乘法原理。。。怎么用呢,用cnt[i]表示n个数在二进制下的第i为1的有多少个,那么为0的自然就是n - cnt个,我们可以这样考虑,让每一个这一位为1的和每一个这一位为0的进行xor,这样是可以产生贡献的。然后就用cnt * (n - cnt)种搭配能让这一位产生贡献(很明显这些搭配没有重复),那么只要让cnt * (n - cnt)乘以1 << k -1(k表示当前是第几个二进制位)就可以得到第k位的和了。

代码如下:

#include <cstdio>
#include <iostream>

using namespace std;

int n;
long long ans, a;
long long cnt[30];

int main(){
    scanf("%d", &n);
    for (int i = 1;i <= n;i ++){
        scanf("%lld", &a);
        for (int i = 1;i <= 20;i ++) if (1 << (i-1) & a) cnt[i]++;
    }
    for (int i = 1;i <= 20;i ++) ans += cnt[i] * (n-cnt[i]) * (1 << i-1);
    printf("%lld", ans);
} 
//tips:别忘了用long long

 

第二题:题目链接

题目大意:给你n个数(n <= 1e5),每个数为ai(每一个a <= 1e9)求每个区间xor值的和,这里吐槽一下,说的数据是1e9实际只有2的16次方的数据(洛谷第一个讨论看见的)

举个例子吧,比如两个数1 2,你需要输出的就是1 + 2 + 1 xor 2 = 6,单独的一个数也叫一个区间。

题解:同样的暴力枚举每一区间复杂度O(n*n),TLE。。。做这道题需要知道若a xor b = c, 那么a xor c = b;(我不会证明,这是xor的运算性质),然后我们同样的向二进制方向想,对于每一位产生贡献进行考虑,若[l, r]这个区间对于这一位能产生贡献的条件就是其中1的个数为奇数且0的个数为偶数,或者0的个数为奇数且1的个数为偶数。那么我们可以求出xor的前缀也就是sum[i]表示前i个数的异或值。那么统计答案只需要求出对于二进制下的每一位这n个前缀中有几个1,用cnt表示,有几个0(也就是n - cnt)然后让每一个1与每一个0组合形成一个区间然后统计就是cnt (n-cnt) 1 << (k -1);

但是这样是错的,你会发现你让他们组合了却没有考虑到1-X这样的区间也就是让还有cnt个也可以产生贡献。所以答案统计应该是(cnt (n - cnt) + cnt) 1 << (k - 1)也就是cnt (n - cnt + 1) 1 <<(k -1);

代码:

#include <cstdio>
#include <iostream>

const int maxn = 100010;

using namespace std;

int n;
long long ans;
long long sum[maxn];

int main(){
    scanf("%d", &n);
    for (int i = 1;i <= n;i ++) scanf("%lld", &sum[i]), sum[i] = sum[i-1] ^ sum[i];
    for (int i = 1;i <= 17;i ++){
        long long cnt = 0;
        for (int j = 1;j <= n;j ++) if (sum[j] & (1 << (i-1))) cnt ++;
        ans += cnt * (n - cnt + 1) * (1 << i - 1);
    }
    printf("%lld", ans);
    return 0;
}
//tips:别忘了开long long

蒟蒻开始跑路。。。

以上是关于数论——异或,两道水题。的主要内容,如果未能解决你的问题,请参考以下文章

各种友(e)善(xin)数论总集(未完待续),从入门到绝望

(字典树3道水题)codeforces 665E&282E&514C

P3909 异或之积

来几道水题 d050: 妳那裡現在幾點了?

Codeforces Round #379 (Div. 2) 总结分享

省赛后的一点碎碎念