P5283 异或粽子 题解
题意:
给定一个长度为 \(n\) 的整数数组 \(a\),求 \(a\) 中前 \(k\) 大的异或区间和之和。
首先对 \(a\) 处理一个异或前缀和数组 \(s\),这样区间 \([l,r]\) 的异或和就转化成了 \(s_{l-1} \oplus s_r\)。题目就变为求 \(n+1\) 个数(包含一个空前缀)两个数异或前 \(k\) 大的值的和。
直接全部枚举然后排序显然会超时,这里我们可以用到一个 trick:对于一个前缀 \(s_x\),一定有与它异或最大的,次大的……第 \(n\) 大的前缀。首先最大的异或值一定是在所有前缀对应的最大值中的最大值,我们取出这个值,次大的异或值一定出自未取的最大值,以及取走的那个前缀的次大值,以此类推。
所以重新审视一下需求:我们需要一个支持快速插入,删除,取最优的数据结构,这里使用堆来维护。
至于如何求出若干个数与一个数异或值第 \(k\) 大的异或值:
首先我们都知道有一道典题:求出若干个数与一个数异或值最大的异或值。
我们将这些数都插入一棵 \(01\) trie,能走反就走反。为了解决这个延申问题,我们同样将这些数都插入一棵 \(01\) trie,但同时处理出每个子树的大小。当一个点有两个儿子时,若其取反子树的大小 \(siz1\) 大于等于查找的排行 \(rk\),我们就走取反。否则走不取反的子树,在不取反的子树中查找第 \(rk-siz1\) 大的值。
当然最后我们会发现一个问题:比如一对数 \(s_i,s_j\),我们会将 \(i\) 匹配 \(j\) 计算一次贡献,也会将 \(j\) 匹配 \(i\) 计算一次贡献,我们只需要查询前 \(2k\) 个数,最后除以二即可。
点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define debug cout<<"DEBUG"<<endl;
#define pb push_back
#define pii pair<int,int>
#define vi vector<int>
#define imp map<int,int>
using namespace std;
const int N=5e5+5,M=2e7+5;
int n,m,son[M][2],siz[M],cnt;
ll a[N],s[N];
struct node{
ll x,rk,val;
bool operator <(const node &k) const {
return val<k.val;
}
};
priority_queue<node>pq;
void insert(ll x){
int p=0;
for(int i=31;i>=0;i--){
int now=(x>>i)&1;
siz[p]++;
if(!son[p][now]){
son[p][now]=++cnt;
}
p=son[p][now];
}
siz[p]++;
}
ll query(ll x,ll rk){
int p=0;
ll ans=0;
for(int i=31;i>=0;i--){
int now=(x>>i)&1;
if(!son[p][now^1]){
p=son[p][now];
}else if(rk<=siz[son[p][now^1]]){
ans+=(1ll<<i);
p=son[p][now^1];
}else{
rk-=siz[son[p][now^1]];
p=son[p][now];
}
}
return ans;
}
int main(){
cin>>n>>m;
m*=2;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]^a[i];
}
for(int i=0;i<=n;i++){
insert(s[i]);
}
for(int i=0;i<=n;i++){
pq.push({i,1,query(s[i],1)});
}
ll ans=0;
while(m--){
node t=pq.top();
pq.pop();
ans+=t.val;
if(t.rk<n) pq.push({t.x,t.rk+1,query(s[t.x],t.rk+1)});
}
cout<<ans/2<<"\n";
return 0;
}
本文来自博客园,作者:Aurora_Borealis,转载请注明原文链接:https://www.cnblogs.com/Aurora-Borealis-Not-Found/p/17407002.html

浙公网安备 33010602011771号