[题解]LuoGu5283:[十二省联考2019]异或粽子

原题传送门
Unbelievable!我也能自己a省选题了?

前几天刚刚做过一道题:BZOJ3689异或之
先讲讲这道题

【BZOJ3689】异或之
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

solution

其实trie也可以实现异或第k大的
可以通过类似平衡树求kth的方法,开个size的桶
把每个数与其他数异或的第k大求出来
然后用优先队列,开个小根堆,当用好一个数与其他数的异或第k大时,把第k+1大加入堆
会有重复,会出现算好x ^ y,然后来y ^ x的情况,所以每两个只能用一个
具体见代码:

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct node{
    int ans, a, id;
    bool operator < (const node & x) const{
        return ans > x.ans;
    }
};
priority_queue <node> q;
int n, m, size[maxn * 30], ch[maxn * 30][3], sz, a[maxn];
  
inline int read(){
    int s = 0, w = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
    for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
    return s * w;
}
  
void insert(int x){
    int u = 0;
    for (int i = 30; i >= 0; --i){
        int c = (x & (1 << i)) != 0;
        u = ch[u][c] ? ch[u][c] : ch[u][c] = ++sz;
        ++size[u];
    }
}
  
int query(int x, int k){
    int u = 0, ans = 0;
    for (int i = 30; i >= 0; --i){
        int c = (x & (1 << i)) != 0;
        if (size[ch[u][c]] >= k) u = ch[u][c]; else ans += (1 << i), k -= size[ch[u][c]], u = ch[u][c ^ 1];
        //如果有k以上个数当前数位与x一样,当然选择异或,因为这样数位上就变0了,否则,得与当前数位上与自己不一样的数异或
    }
    return ans;
}
  
int main(){
    n = read(), m = read();
    for (int i = 1; i <= n; ++i){
        a[i] = read(); insert(a[i]);
    }
    for (int i = 1; i <= n; ++i){
        node tmp;
        tmp.ans = query(a[i], 2);
        //取第2小是因为最小的是自己异或自己,不算
        tmp.a = a[i], tmp.id = 2;
        q.push(tmp);
    }
    for (int i = 1; i <= (m << 1); ++i){
        node tmp = q.top(); q.pop();
        if (i & 1) printf("%d", tmp.ans);//每两个只能用一个
        if (i & 1 && i < (m << 1) - 1) printf(" ");//行末不能有空格
        if (++tmp.id >= n) continue;//id++
        tmp.ans = query(tmp.a, tmp.id);
        q.push(tmp);
    }
    printf("\n");
    return 0;
}
然后开始讲这道异或粽子

solution

刚看完题就感觉跟异或之非常像,不过看到区间异或和就失望了,因为我知道上一题的trie是两个数异或起来,然而这一题是处理一堆数异或起来
唉,好像这个区间必须连续,想到什么?前缀和!
预处理前缀异或和,[l,r]的异或和就是[1,r] ^ [1,l-1]
那么每一段区间的异或和都可以表示为两个前缀异或和的异或和(l=1的情况时,让[1,r]与0异或)

但是上一题是处理前k小,这一题求前k大,没关系,前k大就是倒数k小 ^ - ^
化归到上一题
另外,不要忘了long long

Code:

#include <bits/stdc++.h>
#define maxn 500010
#define int long long
using namespace std;
struct node{
	int a, ans, id;
	bool operator < (const node & x) const{
		return ans < x.ans;
	}
};
priority_queue <node> q;
int a[maxn], n, m, size[maxn * 31], sz, ch[maxn * 31][2];

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void insert(int x){
	int u = 0;
	for (int i = 31; i >= 0; --i){
		int c = (x & (1 << i)) != 0;
		++size[u = ch[u][c] ? ch[u][c] : ch[u][c] = ++sz];
	}
}

int query(int x, int k){
	int u = 0, ans = 0;
	for (int i = 31; i >= 0; --i){
		int c = (x & (1 << i)) != 0;
		if (size[ch[u][c]] >= k) u = ch[u][c]; else k -= size[ch[u][c]], ans += (1LL << i), u = ch[u][c ^ 1];
	}
	return ans;
}
//跟上一题一样的套路

signed main(){
	n = read(), m = read();
	a[0] = 0; insert(0);
	for (int i = 1; i <= n; ++i){
		int x = read();
		insert(a[i] = a[i - 1] ^ x);//前缀异或和
	}
	for (int i = 0; i <= n; ++i){
		node tmp;
		tmp.ans = query(a[i], n + 1);//因为0也参与,所以总共有n+1个数参与,所以取第n+1小个
		tmp.a = a[i], tmp.id = n + 1;
		q.push(tmp);
	}
	int ans = 0;
	for (int i = 1; i <= (m << 1); ++i){
		node tmp = q.top(); q.pop();
		if (i & 1) ans += tmp.ans;
		--tmp.id;
		if (tmp.id == 1) continue;
		tmp.ans = query(tmp.a, tmp.id);
		q.push(tmp);
	}
	printf("%lld\n", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值