LibreOJ 3048 「十二省联考 2019」异或粽子

非常简单的一个套路。

首先考虑求前缀异或和转化问题,即异或前 \(k\) 大的数对。

首先枚举数对的第二个数 \(r = 1,2,\dots,n\),并求出 \([0,r)\) 内和 \(r\) 异或最大的数,根据异或值丢进优先队列。
然后执行 \(k\) 次,每次取出一个数,加入答案;
然后求同一个 \(r\)\([0,r)\) 与其异或中除了之前的值以外最大的数,丢进优先队列。

具体地说,就是同时在优先队列上维护一个排名 \(rk\),表示这个候选项是同一个 \(r\) 中的异或第 \(rk\) 大。
初始时 \(rk = 1\),途中找到加入答案后就令 \(rk \gets rk + 1\)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cstdio>
#include <ext/pb_ds/priority_queue.hpp>
using namespace std;
const int N = 5e5;
const int LG = 32;
int n,k;
unsigned int a[N + 5],xorsum[N + 5];
struct node
{
int sum,ch[2];
} trie[(N << 6) + 10];
int rt[N + 5];
void insert(unsigned int v,int &p)
{
static int tot = 0;
trie[++tot] = trie[p],p = tot,++trie[p].sum;
int cur = p;
for(register int i = LG;i;--i)
{
trie[++tot] = trie[trie[cur].ch[(v >> i - 1) & 1]],trie[cur].ch[(v >> i - 1) & 1] = tot;
cur = trie[cur].ch[(v >> i - 1) & 1];
++trie[cur].sum;
}
}
unsigned int query(unsigned int v,int k,int p)
{
unsigned int ret = 0;
for(register int i = LG;i;--i)
{
if(k <= trie[trie[p].ch[((v >> i - 1) & 1) ^ 1]].sum)
p = trie[p].ch[((v >> i - 1) & 1) ^ 1],ret += (1U << i - 1);
else
k -= trie[trie[p].ch[((v >> i - 1) & 1) ^ 1]].sum,p = trie[p].ch[(v >> i - 1) & 1];
}
return ret;
}
struct note
{
unsigned int v;
int x,k;
inline bool operator<(const note &o) const
{
return v < o.v;
}
};
__gnu_pbds::priority_queue<note> q;
long long ans;
int main()
{
scanf("%d%d",&n,&k);
for(register int i = 1;i <= n;++i)
scanf("%u",a + i),xorsum[i] = xorsum[i - 1] ^ a[i],insert(xorsum[i - 1],rt[i] = rt[i - 1]);
for(register int i = 1;i <= n;++i)
q.push((note){query(xorsum[i],1,rt[i]),i,1});
while(k--)
{
note p = q.top();
q.pop();
ans += p.v,p.v = query(xorsum[p.x],++p.k,rt[p.x]);
q.push(p);
}
printf("%lld\n",ans);
}