思路:
好题!
对于这类题,一个通用的切入口是枚举一个量,然后丢到01字典树里去匹配。
此题做法不止一种,可以枚举 a [ j ] a[j] a[j],然后 1 − ( j − 1 ) 1-(j-1) 1−(j−1)建立一棵前缀字典树, ( j + 1 ) − n (j+1)-n (j+1)−n 建立一棵后缀字典树,然后分情况匹配即可。
但个人认为最简单和最容易理解的还是枚举 a [ k ] a[k] a[k],因为 i < j < k i<j<k i<j<k,故可以将 1 − ( k − 1 ) 1-(k-1) 1−(k−1) 的数丢进字典树,然后用 a [ k ] a[k] a[k]去匹配。
考虑 a [ i ] a[i] a[i]和 a [ k ] a[k] a[k]的关系。
因为他们异或的是一个相同的数 a [ j ] a[j] a[j]且异或后大小不等,则说明a[i],a[k]二进制分解后一定有不相同的位,且最高的不相同位之前的前缀一定对应相同。
所以当
a
[
k
]
a[k]
a[k]在字典树匹配时,假设当前第
i
i
i位的数字为
c
c
c,则:
1
−
(
i
−
1
)
1-(i-1)
1−(i−1)位与
a
[
k
]
a[k]
a[k]相同,第
i
i
i位与
a
[
k
]
a[k]
a[k]不同的数一定是合法的
a
[
i
]
a[i]
a[i]。
对于 a [ j ] a[j] a[j]的限制仅仅在于 a [ j ] a[j] a[j]的第 i i i位,设其数字为 x x x。
若
c
=
0
c = 0
c=0,则
x
=
1
x = 1
x=1可保证
(
a
[
i
]
⨁
a
[
j
]
)
<
(
a
[
j
]
⨁
a
[
k
]
)
(a[i]\bigoplus a[j]) < (a[j]\bigoplus a[k])
(a[i]⨁a[j])<(a[j]⨁a[k])
同理:
c
=
1
c = 1
c=1时,
x
=
0
x = 0
x=0符合题意。
故可以用一个数组 s u m [ i ] [ j ] sum[i][j] sum[i][j]维护第 i i i位为 j j j的数的个数。
最后注意去掉不合法的情况 i > j i>j i>j。
可以维护一个变量 e x t ext ext,当插入 a [ k ] a[k] a[k]时,将其视为某个合法的 a [ i ] a[i] a[i],看前 1 − ( i − 1 ) 1-(i-1) 1−(i−1)个数有多少个数满足成为 a [ j ] a[j] a[j]的情况。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 5e5 + 10;
int sum[35][2],T[A<<5][2],tot,n;
ll ans,cnt[A<<5],ext[A<<5];
void init(){
tot = ans = T[0][0] = T[0][1] = 0;
memset(sum,0,sizeof(sum));
}
inline void calc(int u,ll all){ans += cnt[u]*(cnt[u]-1)/2 + (all-cnt[u])*cnt[u] - ext[u];}
void insert(ll v){
int u = 0;
for(int i=30 ;i>=0 ;i--){
int c = (v>>i)&1;sum[i][c]++;
if(!T[u][c]){
T[u][c] = ++tot;
T[tot][0] = T[tot][1] = 0;
cnt[tot] = ext[tot] = 0;
}
if(T[u][c^1]) calc(T[u][c^1],sum[i][c^1]);
u = T[u][c];
cnt[u]++;
ext[u] += sum[i][c] - cnt[u];
}
}
int main(){
int T;scanf("%d",&T);
while(T--){
init();
scanf("%d",&n);
for(int i=1 ;i<=n ;i++){
int x;scanf("%d",&x);
insert(x);
}
printf("%I64d\n",ans);
}
return 0;
}