Description
给出一个长度为n的序列A[1]~A[n],求满足i < j < k且(A[i]^A[j])<(A[j]^A[k])的三元组(i,j,k)个数
Input
第一行一整数T表示用例组数,每组用例首先输入一整数n表示序列长度,之后输入n个整数A[1]~A[n] (1<=T<=20,1<=sum{n}<=5e5,0<=A[i]<=2^30)
Output
对于每组用例,输出满足条件的三元组个数
Sample Input
1
5
1 2 3 4 5
Sample Output
6
Solution
对于任一对A[i],A[k],如果有满足条件的A[j]存在,假设A[i],A[k]在第x位首次出现不同,那么对于A[j],其前x-1位随意,但第x位需要和A[i]相同,考虑枚举A[i],找合法的A[j]和A[k],因为要i < j < k,故把所有数字按编号从大到小插入到字典树中,令cnt[i][j][k]为前i个数中在第j位是k的个数,sum[p]为经过字典树中p节点的数作为A[k]时,在当前层满足条件的A[j]个数,即A[j]的编号需要小于A[k]且在p节点所处层A[j]的值与A[k]的值不同,num[p]为经过字典树p节点的数的个数,每次插入的数作为A[i]时,考虑其在字典树第j层(即第j位)产生的答案,假设这个数在第j层值为v,所处节点为l,对偶节点为r,那么在当前层与A[i]不同的A[k]有num[r]个,sum[r]表示以经过r的数作为A[k]满足条件的A[j]的数量,但是这些A[j]中有不合法值即为那些编号小于等于i的A[j],这些不合法值共有cnt[i][j][v]个,与num[r]个A[k]组合产生cnt[i][j][v]*num[r]种不合法情况,进而sum[r]-cnt[i][j][v]*num[r]为当前插入的数作为A[i]时在第j层产生的答案数,在统计完后,要把这个数插入到字典树中并更新num值和sum值
Code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=500001;
int ch[maxn*30][2],tot,num[maxn*30],cnt[maxn][30][2],T,n,a[maxn];
ll ans,sum[maxn*30];
//num[i]表示当前经过字典树的第i个节点的数的个数
//sum[i]表示当前字典树第i个节点作为A[k]时,在当前层满足条件的A[j]数(当前位与A[k]不同且编号小于A[k]的编号)
//cnt[i][j][k]表示1~i中在第j位为k的数的个数
void Solve(int i)
{
int p=1;
for(int j=29;j>=0;j--)
{
int v=((a[i]&(1<<j))>0);
int l=ch[p][v],r=ch[p][v^1];
if(r)ans+=sum[r]-(ll)cnt[i][j][v]*num[r];//(j<k)-(j<=i)=i<j<k
if(!l)break;
p=l;
}
p=1;
for(int j=29;j>=0;j--)
{
int v=((a[i]&(1<<j))>0);
if(!ch[p][v])ch[p][v]=++tot;
p=ch[p][v];
num[p]++;//经过p节点的数字多了a[i]
//与经过p节点的A[k](即插入的a[i])在第j层不同的A[j]需要编号小于i且在当前位于A[k]不同
sum[p]+=cnt[i-1][j][v^1];
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
for(int i=1;i<=tot;i++)
{
num[i]=sum[i]=0;
for(int j=0;j<=1;j++)ch[i][j]=0;
}
for(int i=1;i<=n;i++)
for(int j=0;j<=29;j++)
for(int k=0;k<=1;k++)
cnt[i][j][k]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
for(int j=0;j<=29;j++)
for(int k=0;k<=1;k++)
cnt[i][j][k]=cnt[i-1][j][k];
for(int j=0;j<=29;j++)
{
int v=((a[i]&(1<<j))>0);
cnt[i][j][v]++;
}
}
tot=1;ans=0;
for(int i=n;i>=1;i--)Solve(i);
printf("%I64d\n",ans);
}
return 0;
}