异或和(位运算)

时间限制: 1 Sec 内存限制: 128 MB

题目描述
有一个 n 个元素的数组 a ,设f(i,j)=ai xor aj。

现在你要求对于所有的 1≤i≤j≤n 的 f(i,j)之和。
输入
第一行,一个正整数 n 。
接下来 n 个数,表示 ai。
输出
仅一行,一个正整数,表示总和。
样例输入 Copy

3
1 2 3

样例输出 Copy

6

提示
对于 30% 的数据,n≤3000。
另外 20% 的数据,ai≤1。
对于 100% 的数据,1≤n≤2×10^ 5,0≤ai≤10^ 5。
题意很简单,就是求n个数两两异或的和。
常规解法是利用二重循环枚举:

for i=1...n
	for j=i...n
		sum<-sum+a(i)^a(j)
	end
end

这种解法的时间复杂度为O(n^2),数据量过大时不再适用。
由于异或运算是按位异或,可以从二进制入手。由异或的运算性质可知,当两个数不同时,异或的结果为1,而且最后的异或和只与异或结果为1的位有关,因此只需要统计所有的数某一位上两两异或的结果为1的个数,然后用个数乘以当前位的权值,相加就得到答案。那么如何统计某一位上两两异或的结果为1的个数呢?回顾异或的运算性质,设某一位为0和1的个数分别为x、y,则x*y就是要统计的结果。
举个例子:

input:
4
2 3 5 8
转化为二进制:
2=>0 0 1 0
3=>0 0 1 1
5=>0 1 0 1
8=>1 0 0 0
ans[]的值为:1 1 2 2
从每一位来看,所有(0,1)对的个数就是两两异或的结果为1的个数。
(0,1)对的个数:3(=3(0的个数)*1(1的个数)) 3(=3(0的个数)*1(1的个数)) 4(=2(0的个数)*2(1的个数)) 4(=2(0的个数)*2(1的个数))
权值:2^3 2^2 2^1 2^0
异或和=3*2^3+3*2^2+4*2^1+4*2^0=48

注意数组a[]的大小,至少能容下范围内最大的二进制数(100000,17位)。

#include<cstdio>
#include<algorithm>
using namespace std;
int ans[20]={
    
    0};//log2(100000)约为17,这里的数组大小应该开到至少20
long long int res=0;
int main()
{
    
    
    int n,a,i,len,mmax=-1;//mmax表示所有数中最大的二进制位数
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
    
    
        scanf("%d",&a);
        len=0;
        while(a)
        {
    
    
            len++;
            if(a&1)ans[len]++;//记录当前位为1的个数
            a=a>>1;
        }
        mmax=max(mmax,len);
    }
    for(i=1;i<=mmax;i++)
    {
    
    
        res+=(long long int)(1<<(i-1))*(long long int)ans[i]*(long long int)(n-ans[i]);//强转为long long
    }
    printf("%lld",res);
    return 0;
}
/**************************************************************
    Language: C++
    Result: 正确
    Time:39 ms
    Memory:1120 kb
****************************************************************/

猜你喜欢

转载自blog.csdn.net/upc122/article/details/106535437