BZOJ[3689] 异或之 Trie树+堆

4 篇文章 0 订阅
2 篇文章 0 订阅

题目链接http://www.lydsy.com/JudgeOnline/problem.php?id=3689

Description

给定n个非负整数A[1], A[2], ……, A[n]。
对于每对(i, j)满足1 <= i < j <= n,得到一个新的数A[i] xor A[j],这样共有n*(n-1)/2个新的数。求这些数(不包含A[i])中前k小的数。
注:xor对应于pascal中的“xor”,C++中的“^”。

Input

第一行2个正整数 n,k,如题所述。
以下n行,每行一个非负整数表示A[i]。

Output

共一行k个数,表示前k小的数。

Sample Input

4 5

1

1

3

4

Sample Output

0 2 2 5 5

HINT

【样例解释】

1 xor 1 = 0 (A[1] xor A[2])

1 xor 3 = 2 (A[1] xor A[3])

1 xor 4 = 5 (A[1] xor A[4])

1 xor 3 = 2 (A[2] xor A[3])

1 xor 4 = 5 (A[2] xor A[4])

3 xor 4 = 7 (A[3] xor A[4])

前5小的数:0 2 2 5 5

【数据范围】

对于100%的数据,2 <= n <= 100000; 1 <= k <= min{250000, n*(n-1)/2};

    0 <= A[i] < 2^31


Trie树+堆
可以对每一个数的二进制建一棵Trie树,这样就可以通过每一个节点的 size 快速查询出一个数与其它数异或值的第 k
维护一个小根堆,将每个数异或的第二小扔入堆中(第一小是它自己),表示是与这个数异或出的第二小的数,在取出堆顶的同时,则取出的数数可以看做与id异或起来第 num 小的数被扔入堆中的,然后再将 id 与其他数异或出的第 (num+1) 小的数再扔入堆中
然而这样会有重复的答案, a ^b b ^a都会被记作答案,可以找出 2k 个数,肯定有一半是重复的,所以可以隔两个一输出即可

代码如下:

#include<algorithm>
#include<cstdio>
#include<queue>
#define N 4000010
using namespace std;
const int maxx=32;
int ch[N][2],size[N];
int a[N];
int top,n,m,k,ans,x;
struct Elem{
    int w,id,num;
    bool operator > (const Elem b) const{
        return w>b.w;
    }
    Elem(){}
    Elem(int _,int __,int ___):w(_),id(__),num(___){}
};
priority_queue<Elem,vector<Elem>,greater<Elem> > q;
void add(int t){
    x=0;
    for(int i=maxx-1;i>=0;i--){
        int p=t>>i&1;
        if(!ch[x][p]) ch[x][p]=++top;
        size[x=ch[x][p]]++;
    }
}
int query(int id,int k){
    ans=x=0;
    for(int i=maxx-1;i>=0;i--){
        int p=a[id]>>i&1;
        if(size[ch[x][p]]>=k) x=ch[x][p];
        else ans+=(1<<i),k-=size[ch[x][p]],x=ch[x][p^1];
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    for(int i=1;i<=n;i++) add(a[i]);
    for(int i=1;i<=n;i++) q.push(Elem(query(i,2),i,2));
    for(int i=1;i<=2*k;i++){
        Elem t=q.top();q.pop();
        if(i&1) printf("%d ",t.w);
        if(t.num==n) continue;
        t.w=query(t.id,++t.num);
        q.push(t);
    }
return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值