题目描述
SmallBell最近沉迷Lovelive,苦于[Beat In Angel]的fc。他在研究用什么队伍的时候,顺便把队伍的Cool属性也记录了下来,于是他得到 nnn 个数。
旁边的WerKeyTom_FTD正苦于如何卡常数。他突然对SmallBell提了个问题:选择两个数 i,ji,ji,j (i≠j)(i \neq j)(i̸=j) ,把第 i,ji,ji,j 个数异或起来,求第 kkk 大的异或值是多少。注意: (i,j)(i,j)(i,j) 和 (j,i)(j,i)(j,i) 是不同方案。
SmallBell:好水啊,一下就切了!然后把问题交给了你。
数据范围
除 kkk 外所有数为不大于 5×1045 \times 10^45×104 的正整数, 1≤k≤n×(n−1)1 \le k \le n \times (n-1)1≤k≤n×(n−1)
题解
建立 trietrietrie 树,考虑二分答案,然后每个数在 trietrietrie 树上找出异或值 ≥mid≥mid≥mid 的个数总和,然后和 kkk 判断即可,效率: O(nlog2n)O(nlog^2n)O(nlog2n)
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=5e4+5;
int n,a[N],l,r=(1<<16)-1,d,t=1;
LL k;
struct O{
int ch[2],sz;
}tr[N*50];
void ins(int x){
int p=1;
for (int v,i=15;~i;i--){
v=(x>>i)&1;
if (!tr[p].ch[v])
tr[p].ch[v]=++t;
p=tr[p].ch[v];tr[p].sz++;
}
}
int get(int x){
int p=1,sum=0;
for (int v,i=15;~i;i--){
v=(x>>i)&1;
if (d&(1<<i))
p=tr[p].ch[v^1];
else
sum+=tr[tr[p].ch[v^1]].sz,
p=tr[p].ch[v];
}
return sum+tr[p].sz;
}
bool J(){
LL sum=0;
for (int i=1;i<=n;i++)
sum+=1ll*get(a[i]);
return (sum>=k);
}
int main(){
scanf("%d%lld",&n,&k);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]),ins(a[i]);
while(l<r){
d=(l+r+1)>>1;
if (J()) l=d;
else r=d-1;
}
return printf("%d\n",l),0;
}