P5283 [十二省联考 2019] 异或粽子 题解
\(Description\)
\(Solution\)
01trie + 堆
考虑对原数组做个前缀异或和,然后原问题就变成了找 \(k\) 对不都相同的点对,使它们异或起来的和最大。
显然我们要找出前 \(k\) 大的点对 \((l, r)\),但是 \(l\) 要小于 \(r\) ,非常恶心。
所以我们要把它翻一下变成矩阵,找前 \(2 \times k\) 大的点对,最后答案再除以 2 即可。
对于每一行 \(l\),先把最大的 \(val(l, r_1)\) 插到堆里,然后取堆顶加到答案里,再把堆顶元素所在行次大的的 \(val(l, r_2)\) 插到堆里,重复此操作。
注意要开 \(long \ long\)
\(Code\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 5e5 + 10;
int n, k;
ll ans;
int a[N];
int trie[N << 5][2], siz[N << 5], tot;
struct node{
int id, rk;
ll val;
bool operator < (const node &b) const{
return val < b.val;
}
};
priority_queue <node> q;
inline void insert(int x){
int u = 0;
for(int i = 31; i >= 0; --i){
int t = (x >> i) & 1;
siz[u]++;
if(!trie[u][t]) trie[u][t] = ++tot;
u = trie[u][t];
}
siz[u]++;
}
inline ll query(ll x, int rk){
int u = 0;
ll res = 0;
for(int i = 31; i >= 0; --i){
int t = (x >> i) & 1;
if(!trie[u][t ^ 1]) u = trie[u][t];
else if(rk <= siz[trie[u][t ^ 1]]) u = trie[u][t ^ 1], res |= (1ll << i);
else rk -= siz[trie[u][t ^ 1]], u = trie[u][t];
}
return res;
}
signed main(){
n = read(), k = read(); k <<= 1;
for(int i = 1; i <= n; ++i) a[i] = read() ^ a[i - 1];
for(int i = 0; i <= n; ++i) insert(a[i]);
for(int i = 0; i <= n; ++i) q.push((node){i, 1, query(a[i], 1)});
for(int i = 1; i <= k; ++i){
node now = q.top(); q.pop();
ans += now.val;
if(now.rk < n) q.push((node){now.id, now.rk + 1, query(a[now.id], now.rk + 1)});
}
write((ans + 1) >> 1), puts("");
return 0;
}
\[\_EOF\_
\]