BZOJ3689 异或之

题面

题目描述

给定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++中的“^”。

输入

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

输出

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

样例输入

4 5
1
1
3
4

样例输出

0 2 2 5 5

【样例解释】

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树上可以找到一个数的xor第k大(小).
我们把每个数的异或第\(2\)小先装进堆里面(异或第\(1\)小是自己异或自己, 不在题目考虑范围内), 每次取出堆头, 假设堆头是第\(p\)小, 则插入第\(p + 1\)小即可.
注意应该取\(2k\)次, 因为每两次得到的结果是相同的.

#include <cstdio>
#include <cctype>
#include <queue>

namespace Zeonfai
{
    inline int getInt()
    {
        int a = 0, sgn = 1;
        char c;
        while(! isdigit(c = getchar()))
            if(c == '-')
                sgn *= -1;
        while(isdigit(c))
            a = a * 10 + c -'0', c = getchar();
        return a * sgn;
    }

    inline void print(int a)
    {
        if(! a)
            return;
        print(a / 10);
        putchar(a % 10 + '0');
    }

    inline void println(int a)
    {
        if(a < 0)
            putchar('-'), a *= -1;
        if(a == 0)
            putchar('0');
        print(a);
        putchar(' ');
    }
}

const int N = 100000;

struct trieTree
{
    struct node
    {
        node *suc[2];
        int cnt;

        inline node()
        {
            suc[0] = suc[1] = NULL;
            cnt = 0;
        }
    };

    node *rt;

    inline trieTree()
    {
        rt = new node;
    }

    inline void insert(int w)
    {
        node *u = rt;
        for(int i = 30; ~ i; -- i)
        {
            int k = w >> i & 1;
            if(u->suc[k] == NULL)
                u->suc[k] = new node;
            ++ (u = u->suc[k])->cnt;
        }
    }

    inline int query(int w, int k)
    {
        node *u = rt;
        int res = 0;
        for(int i = 30; ~ i; -- i)
        {
            int tmp = w >> i & 1;
            if(u->suc[tmp] != NULL && u->suc[tmp]->cnt >= k)
                u = u->suc[tmp];
            else
                k -= u->suc[tmp] == NULL ? 0 : u->suc[tmp]->cnt, u = u->suc[tmp ^ 1], res += 1 << i;
        }
        return res;
    }
}trie;

struct record
{
    int id, k, w;

    inline record(int _id, int _k ,int _w)
    {
        id = _id, k = _k, w = _w;
    }

    inline int friend operator <(record a, record b)
    {
        return a.w > b.w;
    }
};

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("BZOJ3689.in", "r", stdin);
    #endif
    using namespace Zeonfai;
    int n = getInt(), k = getInt();
    static int a[N];
    for(int i = 0; i < n; ++ i)
        trie.insert(a[i] = getInt());
    static std::priority_queue<record> hp;
    for(int i = 0; i < n; ++ i)
        hp.push(record(i, 2, trie.query(a[i], 2)));
    for(int i = 0; i < k << 1; ++ i)
    {
        record res = hp.top();
        hp.pop();
        if(i & 1)
            println(res.w);
        if(res.k < n)
            hp.push(record(res.id, res.k + 1, trie.query(a[res.id], res.k + 1)));
    }
}

转载于:https://www.cnblogs.com/ZeonfaiHo/p/7115232.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值