题目大意
给一个长度为 nnn 的数组 {an}\{ a_n \}{an} 并给定一个整数 kkk, 求由 kkk 个不同的子区间的异或和之和的最大值。
算法分析
先做一个前缀的异或和:
si=⨁i=1nsi s_i = \bigoplus_{i = 1}^n s_i si=i=1⨁nsi
类似于前缀和我们就有:
⨁i=lrai=sr−sl−1 \bigoplus_{i = l}^ra_i = s_r - s_{l - 1} i=l⨁rai=sr−sl−1
那么问题就转化成了求 kkk 对不同的点对 (x,y)(x, y)(x,y),其中 x≠yx \neq yx=y,最大化:
∑i=1k(sxi⨁syi) \sum_{i = 1}^k (s_{x_i} \bigoplus s_{y_i}) i=1∑k(sxi⨁syi)
于是很好象到一种 O(n2logn)O(n^2\log n)O(n2logn) 的做法,枚举所有点对 (x,y)(x, y)(x,y),然后求出异或值,再扔到一个堆里面,最后从堆顶取 kkk 个数出来然后再加起来就完了。
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1010
#define int long long
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int s[MAXN] = { 0 };
priority_queue<int> q;
int n = 0; int k = 0;
signed main(){
n = in; k = in; int ans = 0;
for(int i = 1; i <= n; i++) a[i] = in;
for(int i = 1; i <= n; i++) s[i] = (s[i - 1] ^ a[i]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
q.push(s[i] ^ s[j - 1]);
for(int i = 1; i <= k; i++){
ans += q.top(); q.pop();
} cout << ans << '\n';
return 0;
}
这样就能有 60pts60pts60pts 的高分。
但是这个算法仍然不够优秀,我们考虑优化这个算法。首先我们发现找点对这件事情就是处理三角形上的最大,我们考虑将三角形扩大两倍变成正方形然后算 2k2k2k 个点对的最大值,最后的答案再除以一个 222 就是我们要的答案。然后我们又发现对角线上的点对,也就是 (i,i)(i, i)(i,i) 对答案的贡献都是 000,所以现在我们也不需要考虑 x≠yx \neq yx=y 这个条件了。
这样之后就很好处理了,首先我们显然能用一个 0/1 trie0/1 \; trie0/1trie 在 O(logsi)O(\log s_i)O(logsi) 的时间内求出在 sss 中与 sis_isi 异或的第 kkk 大值:
void build(int x) {
int p = 0;
for(int i = 31; i >= 0; i--){
int ch = (x >> i) & 1; size[p]++;
if(!trie[p][ch]) trie[p][ch] = ++tot;
p = trie[p][ch];
}
size[p]++;
}
int query(int x, int rk){
int p = 0; int ans = 0;
for(int i = 31; i >= 0; i--){
int ch = (x >> i) & 1;
if(!trie[p][ch ^ 1]) p = trie[p][ch]; // 如果没有相反的直接跑
else if(rk <= size[trie[p][ch ^ 1]]) p = trie[p][ch ^ 1], ans |= 1ll << i; // 相反的子树大小大于等于当前值
else rk -= size[trie[p][ch ^ 1]], p = trie[p][ch];
}
return ans;
}
然后我们维护一个堆,首先把 sis_isi 初始在 sss 中的异或的最大值插入堆中,然后每次取出堆顶,并累积答案,如果我们取出的是 sis_isi 在 sss 中的第 kkk 大,那么我们就把 k+1k + 1k+1 大加入堆中,这样就能保证次大的那个数一定在堆里面。这样操作 2k2k2k 次就能得到答案了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 20002000
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int s[MAXN] = { 0 };
int n = 0; int k = 0;
int tot = 0;
int trie[MAXN][5] = { 0 };
int size[MAXN] = { 0 };
void build(int x) {
int p = 0;
for(int i = 31; i >= 0; i--){
int ch = (x >> i) & 1; size[p]++;
if(!trie[p][ch]) trie[p][ch] = ++tot;
p = trie[p][ch];
}
size[p]++;
}
int query(int x, int rk){
int p = 0; int ans = 0;
for(int i = 31; i >= 0; i--){
int ch = (x >> i) & 1;
if(!trie[p][ch ^ 1]) p = trie[p][ch]; // 如果没有相反的直接跑
else if(rk <= size[trie[p][ch ^ 1]]) p = trie[p][ch ^ 1], ans |= 1ll << i; // 相反的子树大小大于等于当前值
else rk -= size[trie[p][ch ^ 1]], p = trie[p][ch];
}
return ans;
}
struct Tnode{
int dat;
int id, rk;
bool operator < (const Tnode &rhs) const { return dat < rhs.dat; }
};
priority_queue<Tnode> q;
signed main(){
n = in; k = in; k <<= 1; int ans = 1;
for(int i = 1; i <= n; i++) a[i] = in;
for(int i = 1; i <= n; i++) s[i] = s[i - 1] ^ a[i];
for(int i = 0; i <= n; i++) build(s[i]); // 注意有 0
for(int i = 0; i <= n; i++) q.push((Tnode){ query(s[i], 1), i, 1 }); // 0
for(int i = 1; i <= k; i++){
Tnode t = q.top(); ans += t.dat; q.pop();
if(t.rk < n) q.push((Tnode){ query(s[t.id], t.rk + 1), t.id, t.rk + 1 });
}
cout << (ans >> 1) << '\n';
return 0;
}

这篇博客介绍了如何优化算法来解决一个数组问题,即给定一个数组和一个整数k,求不同子区间的异或和之和的最大值。通过前缀异或和的计算,将问题转化为寻找特定数量的最大异或值点对。博主提出了一种初始的O(n^2 log n)解决方案,并进一步优化为O(n log^2 n),利用0/1 trie数据结构动态维护最大异或值,并使用优先队列实现。最终实现了一个更高效的算法来找到答案。
1768

被折叠的 条评论
为什么被折叠?



