【题目】
LOJ
给定一个序列
a
i
a_i
ai,一个区间的价值是其
a
i
a_i
ai异或值。选择不同的
k
k
k个区间使得价值之和最大。
n
≤
5
×
1
0
5
,
k
≤
2
×
1
0
5
,
a
i
<
2
3
2
n\leq 5\times 10^5,k\leq 2\times 10^5,a_i<2^32
n≤5×105,k≤2×105,ai<232
【解题思路】
据说是大原题。
只求一个区间最大异或值我们可以通过计算前缀异或值,丢到
Trie
\text{Trie}
Trie简单得到。
现在虽然不仅要求不同,而且要求
k
k
k个,但其实并不难。
考虑到 k k k并不大,我们不妨依次取出所有的最值。一种可行的思路是维护一个堆,堆里面存以每个位置为右端点能得到的最大值,每次取出一个值我们就放入对应右端点的下一个值。也就是说,现在的问题就是取出以一个位置为右端点时的第 k k k大异或值。
不妨对前缀建立可持久化 Trie \text{Trie} Trie,每次在 Trie \text{Trie} Trie上二分即可。
复杂度 O ( ( n + k ) log A ) O((n+k)\log A) O((n+k)logA)
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int ui;
const int N=5e5+10,M=N*33;
ui read()
{
ui ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
namespace Trie
{
int rt[N];
ui fc[33];
struct Trie
{
int sz,cnt[M],ch[M][2];
void insert(int x,int y,ui num)
{
for(int i=31;~i;--i)
{
int now=(num&fc[i])?1:0;
ch[x][now^1]=ch[y][now^1];
ch[x][now]=++sz;cnt[sz]=cnt[ch[y][now]]+1;
x=ch[x][now];y=ch[y][now];
}
}
ui query(int x,int k,ui num)
{
ui res=0;
for(int i=31;~i;--i)
{
int now=(num&fc[i])?1:0;
if(cnt[ch[x][now^1]]>=k) res+=fc[i],x=ch[x][now^1];
else k-=cnt[ch[x][now^1]],x=ch[x][now];
}
return res;
}
}T;
}
using namespace Trie;
namespace DreamLolita
{
int n,K;
ui a[N];
ll ans;
struct data
{
ui val;int k,p;
data(ui _v=0,int _k=0,int _p=0):val(_v),k(_k),p(_p){}
bool operator <(const data&rhs)const{return val<rhs.val;}
};
priority_queue<data>q;
void solution()
{
fc[0]=1;for(int i=1;i<32;++i)fc[i]=fc[i-1]<<1;
n=read();K=read();
for(int i=1;i<=n;++i) a[i]=read()^a[i-1],rt[i]=++T.sz,T.insert(rt[i],rt[i-1],a[i-1]);
for(int i=1;i<=n;++i) q.push(data(T.query(rt[i],1,a[i]),1,i));
while(K--)
{
data x=q.top();q.pop();
ans+=x.val;
if(x.k<x.p) q.push(data(T.query(rt[x.p],x.k+1,a[x.p]),x.k+1,x.p));
}
printf("%lld\n",ans);
}
}
int main()
{
#ifdef Durant_Lee
freopen("LOJ3048.in","r",stdin);
freopen("LOJ3048.out","w",stdout);
#endif
DreamLolita::solution();
return 0;
}