传送门:HDU-6059
题目要求计算Ai^Aj < Aj^Ak的种数
题解:字典树
对于这道题首先考虑到,假如Ai和Ak前t-1位都相同,第t位不相同时,如果Ai的第t位等于Aj的第t位,Ak的第t位不等于Aj的第t位,那么Ai^Aj一定小于Aj^Ak的,那么只要在字典树中从高位往低位依次考虑即可。
v表示这个节点添加的次数,sum表示在这个数的前面有多少个数的前t-1位与这个数的前t-1位是不同的(第t位相同),cnt[i][0/1]表示有多少个数字的第i位是0/1
枚举Ak的每一位,对于Ak的第t位记作tmp
现在只讨论如果tmp==1时对答案的贡献(tmp==0时的求法相同)
L[now].nxt[tmp^1].v*(L[now].nxt[tmp^1].v-1)/2+(cnt[t][tmp^1]-L[now].nxt[tmp^1].v)*L[now].nxt[tmp^1].v-L[now].sum[tmp^1]
1)前一部分表示Ai,Aj和Ak的前t-1位都相同时对答案的贡献
2)中间一部分是Ai的前t-1位与Ak的前t-1相同,Aj的前t-1位不与Ak的前t-1相同时对答案的贡献(乍看一下第二部分没有问题,其实里面包含了非法部分,即有些前t-1位与Ak不相同的数被记作了Ai,因此需要第三部分)
3)后面一部分表示Ai的前t-1位与Ak的前t-1位不同时对答案的贡献(为负数,因为计算时规定了Ai的前t-1位要与Ak的前t-1位相同)
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<set>
#include<vector>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define first x
#define second y
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int inf = 0x3f3f3f3f;
const int MX = 5e7 + 5;
struct node {
int nxt[2];
LL sum[2];
int v;
void init() {
sum[0] = sum[1] = 0;
nxt[0] = nxt[1] = -1;
v = 0;
}
} L[MX];
int tot;
LL ans, cnt[32][2];
void add(int a[], int len) {
int now = 0, tmp;
for (int i = len - 1; i >= 0; i--) {
tmp = a[i];
if (L[now].nxt[tmp] == -1) {
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;
ans += L[L[now].nxt[tmp ^ 1]].v * (cnt[i][tmp ^ 1] - L[L[now].nxt[tmp ^ 1]].v);
ans -= L[now].sum[tmp ^ 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() {
//freopen("in.txt", "r", stdin);
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;
}