题目大意
给定一个n个正整数的数组A[],求 ∑i≠jlg(A[i]xorA[j])
n≤50000 1≤A[i]≤ 1018
分析
首先每个数不会很大,那么任意一个异或起来的数的lg值不会超过18
然后可以考虑lg的值,假设是x,然后可以确定一个区间
[10x,10x+1)
,如果异或值在这个区间内,它对答案的贡献就是x。
然后就是求多少个异或值在一个区间内,做法就是套路:可以先把所有数放进二进制trie里,然后枚举每个数,在trie上跑即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=50005,M=N*62;
typedef unsigned long long LL;
int n,tot,son[M][2],cnt[M];
LL Ans[N],A[N],s,p[N];
char c;
LL read()
{
for (c=getchar();c<'0' || c>'9';c=getchar());
LL x=c-48;
for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
return x;
}
void insert(LL x,int y,int z)
{
if (y<0) return;
int w=((x & p[y])>0);
if (!son[z][w]) son[z][w]=++tot;
cnt[son[z][w]]++;
insert(x,y-1,son[z][w]);
}
int get(LL x,LL k,int y,int z)
{
if (y<0 || !z) return cnt[z];
int w=((x & p[y])>0);
if ((k & p[y])==0) return get(x,k,y-1,son[z][w])+cnt[son[z][1-w]];
return get(x,k,y-1,son[z][1-w]);
}
int main()
{
scanf("%d",&n);
p[0]=1;
for (int i=1;i<=62;i++) p[i]=p[i-1]*2;
tot=1;
for (int i=0;i<n;i++)
{
A[i]=read();
insert(A[i],61,1);
}
for (LL i=1,t=10;t<=1e18;i++,t*=10)
{
Ans[i]=(LL)n*(n-1);
for (int j=0;j<n;j++) Ans[i]-=get(A[j],t,61,1);
if (i>0) s+=(i-1)*(Ans[i]-Ans[i-1]);
if (t==1e19) break;
}
s+=((LL)n*(n-1)-Ans[18])*18;
printf("%lld\n",s);
return 0;
}