Problem Description
Give you an array A[1..n],you need to calculate how many tuples (i,j,k) satisfy that (i<j<k) and (
Ai
xor
Aj
)< (
Aj
xor
Ak
)
There are T test cases.
1≤T≤20
1≤∑n≤5∗105
0≤A[i]<230
Input
There is only one integer T on first line.
For each test case , the first line consists of one integer n ,and the second line consists of n integers which means the array A[1..n]
Output
For each test case , output an integer , which means the answer.
Sample Input
1
5
1 2 3 4 5
Sample Output
6
Source
2017 Multi-University Training Contest - Team 3
题意:
给你数组长度为n的数组A,求出三元组(i,j,k)的个数,满足( Ai xor Aj )< ( Aj xor Ak )
想法:
我们来枚举k的值,运用字典树。
我们假设
Ai
与
Ak
的最高不同位为第t位,因为
Ai
与
Ak
第t位之前的一定是全部相同的,所以只要
Aj
的第t位与
Ai
相等异或此位结果为0,
Aj
的第t位与
Ak
相反此位异或结果为1,就能满足异或不等式。
从而
Aj
第t位之前是随意取的,因为
Ai
与
Ak
第t位之前的一定是全部相同的,二者与
Aj
第t位之前异或的结果是相等的;
Aj
第t位之后也是随意取的,因为上一段已经确定了
Aj
第t位的值了,之后的位异或后再大再小都不会影响高位。
下面画个图举个栗子:
计算1 2 6 8 10 中的三元组个数,先画个字典树 好说明一下:
我们从第t位着手,分为两种情况。
1. A[i],A[j],A[k]三者前t-1位都相同。这一部分我们通过分析发现,以与t同父结点的结点为根的子树中,所有的结点都可以选作A[i],A[j],并且默认顺序小的做i,大的做j。故假设这一部分有n个结点,结果应为C(n,2),即n*(n-1)/2。(比如图中A[k]是当前的0110,那A[i]和A[j]可以从0001,0010中任选2个分别作为A[i]和A[j],当然此处只有2个能选+_+)
2. A[i]与A[k]的前t-1位相同,但A[i]与A[j]的前t-1位不同。这时我们可以从,以与t同父结点的结点为根的子树中选取i,然后为保证A[i]与A[j]的第t位相同,我们需要找到与t在同一层(同一深度)中,与A[i]的第t位相同的结点就可以,再从以这些结点为根的子树中找j。(比如A[k]是当前的0110,A[i]选择左子树的0010,A[j]选择右子树的1000)
但仔细分析我们不难发现,i和j的取值和顺序此时是随意的,但是题目要求i和j的取值要符合i
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],num[30];
int cnt[2][31];///cnt[i][j]表示第j位上是i的有多少个
ll ans;
int tsize;
struct node
{
int v;///表示这个节点被经过的次数(数中某位为此节点的个数)
int nxt[2];///此节点表示的值
int ext;
} T[maxn*31];
void solve()
{
int cou=0;///表示树的层数 即第几位 枚举二进制数的每一位
for(int i=0;i<30;++i)
{
if(!T[cou].nxt[num[i]])///给存在数的位置上编号?
{
++tsize;///每个节点的编号
T[cou].nxt[num[i]]=tsize;
}
if(T[cou].nxt[1^num[i]])///找到最高位不同的位置(t)
{
ans+=T[T[cou].nxt[1^num[i]]].v*1ll*(T[T[cou].nxt[1^num[i]]].v-1)/2;///和从k不同的此位上的数里C(v,2) v表示有几个数此位和k不同(小的做i 大的做j)
ans+=T[T[cou].nxt[1^num[i]]].v*1ll*(cnt[1^num[i]][i]-T[T[cou].nxt[1^num[i]]].v);///左边拿一个 右边拿一个(但是i j的顺序是随意的)
ans-=T[T[cou].nxt[1-num[i]]].ext;///在此节点的前面有多少个节点的前t-1位与这个数的前t-1位是不同的(第t位相同) (i成为了不同的这些大数的情况)
}
cou=T[cou].nxt[num[i]];
T[cou].v++;
T[cou].ext+=cnt[num[i]][i]-T[cou].v;
}
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(T,0,sizeof T);
memset(num,0,sizeof num);
memset(cnt,0,sizeof cnt);
ans=tsize=0;
for(int i=0; i<n; ++i)
{
scanf("%d",&a[i]);
for(int j=29; j>=0; --j)
{
num[j]=a[i]%2;
cnt[a[i]%2][j]++;
a[i]/=2;
}
solve();
}
printf("%lld\n",ans);
}
}
ps:代码好拿写QAQ特别是ext排除i>j那里,debug一整天恍恍惚惚。