Kanade's trio
There are T test cases.
1≤T≤20
1≤∑n≤5∗105
0≤A[i]<230
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]
1 5 1 2 3 4 5
6
题意:
给定一个数组A,取一个三元组(i,j,k),其中i<j<k,使得( A[i] xor A[j] ) < ( A[j] xor A[k] ),(xor为异或)。求这个三元组有多少种取法。
思路:
用字典树处理。先在字典树中记录了A中的前k-1个数,现在以处理第k个数为例。
首先由异或的特点(相异为1相同为0)我们可以得出,若使( A[i] xor A[j] ) < ( A[j] xor A[k] ),我们可以将A[k]与A[i]的二进制数位位对应,然后从高位开始,找到第一个不同的位置,记此位为t,(即记录二进制不同的最高位为t),此时只要保证A[i]与A[j]的第t位相同,就可以找到符合题意的情况。
{说明:保证A[i]与A[j]的第t位相同是指,一种情况为A[i]与A[j]前t位均相同,则很容易就得出A[i] xor A[j]的结果是前t位为0,而A[j] xor A[k]的结果是前t-1位为0,t位为1,则符合题意;另一种是A[i]与A[j]前t-1位不同,第t位相同,则我们可以得出A[i] xor A[j]的结果前t-1位与A[j] xor A[k]结果相同,而第t位A[i] xor A[j]=0,而A[j] xor A[k]=1,所以也符合题意。}
然后我们从t位着手,找到合适的i,j使符合题意。由上分析可得,结果可分两个部分计算。
1. A[i],A[j],A[k]三者前t-1位都相同。这一部分我们通过分析发现,以与t同父结点的结点为根的子树中,所有的结点都可以选作i,j。故假设这一部分有n个结点,(n个结点中任取,最后小的是选作i,大的选作j,就符合题目中i<j的条件,其中因为到现在为止只记录了k个数,所以i,j都从前k-1个数中选取,自然符合i<k,j<k)。结果应为C(n,2),即n*(n-1)/2,可得程序中公式1。
2. A[i]与A[k]的前t-1位相同,但A[i](A[k])与A[j]的前t-1位不同。这时我们可以从,以与t同父结点的结点为根的子树中选取i,然后为保证A[i]与A[j]的第t位相同,我们需要找到与t在同一层(同一深度)中,与A[i]的第t位相同的结点就可以,再从以这些结点为根的子树中找j。由此可得程序中公式2。但仔细分析我们不难发现,在公式2的计算方法中,i和j的取值和顺序是随意的,但是题目要求i和j的取值要符合i<j,所以我们就计算了非法部分,需要减去这一部分才是正确的结果。由此可得程序中公式3。
下面贴上代码:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int MX = 5e7 + 5;
struct node
{
int nxt[2];
LL sum[2];
int v; //表示这个节点添加的次数
void init()
{
sum[0] = sum[1] = 0; //在这个数的前面有多少个数的前t-1位与这个数的前t-1位是不同的(第t位相同)
nxt[0] = nxt[1] = -1;
v = 0;
}
} L[MX];
int tot;
LL ans, cnt[32][2]; //cnt[i][0/1]表示有多少个数字的第i位是0/1
void add(int b[], int len)
{
int now = 0, tmp;
for (int i = len - 1; i >= 0; i--)
{
tmp = b[i]; //枚举A二进制每一位,记作tmp
if (L[now].nxt[tmp] == -1) //进if,说明两个数(b[],b[]),i位置tmp的值不一样
{
L[++tot].init();
L[now].nxt[tmp] = tot;
}
if (L[now].nxt[tmp ^ 1] != -1) //找到最高位不同的位置
{
ans += L[L[now].nxt[tmp ^ 1]].v * (L[L[now].nxt[tmp ^ 1]].v - 1) / 2;
//Ai,Aj和Ak的前t-1位都相同时对答案的贡献
ans += L[L[now].nxt[tmp ^ 1]].v * (cnt[i][tmp ^ 1] - L[L[now].nxt[tmp ^ 1]].v);
//Ai的前t-1位与Ak的前t-1相同,Aj的前t-1位不与Ak的前t-1相同时对答案的贡献
ans -= L[now].sum[tmp ^ 1];
//Ai的前t-1位与Ak的前t-1位不同时对答案的贡献(负,第二种情况中误加)
}
L[now].sum[tmp] += cnt[i][tmp] - L[L[now].nxt[tmp]].v;
cnt[i][tmp] ++;
now = L[now].nxt[tmp];
L[now].v++;
}
}
int a[500005], b[35];
int main()
{
int T, n;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(cnt, 0, sizeof(cnt));
tot = ans = 0;
L[0].init();
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < 30; j++)
b[j] = a[i] >> j & 1;
add(b, 30);
}
printf("%lld\n", ans);
}
return 0;
}