[ABC308G] Minimum Xor Pair Query
思路
本题的线段树分治做法显然:维护每个元素存在的时间段,然后每进入线段树的一个节点时在 Trie 树上花 $\log V$ 的时间求出已有元素与它的最小异或值,放入一个堆中。离开一个节点时在堆中删除之前加入的最小异或值。
可删除堆实际上要用两个普通堆维护,代码如下:
struct Large_Root_Heap{
priority_queue<ll> q, d; ll sz, s;
void upd(){while(!d.empty() && q.top() == d.top()) d.pop(), q.pop();}
ll top(){upd(); return q.top();}
void ins(ll x){q.push(x), s += x, sz++;}
void del(ll x){d.push(x), s -= x, sz--;}
} h[N];
另一个做法就是纯可重集做法,重在发现性质。
对于三个非负整数 $x,y,z(x < y < z)$,有 $\min(x \bigoplus y,y\bigoplus z)<x\bigoplus z$。
-
证明
从二进制最高位开始 $i=\log V$,对 $x,y,z$ 进行如下操作:
-
若它们的当前位都两两相同,继续跳到下一位
i--
。 -
根据三个数的大小关系,可以得到两种情况:
- $x_i=0,y_i=0,z_i=1$
显然,这时有 $x\bigoplus y<x\bigoplus z$。
- $x_i=0,y_i=1,z_i=1$
有 $y\bigoplus z<x\bigoplus z$。
-
有了上述性质,我们只需要维护大小相邻的数的异或和就行了。
开两个可重集。可重集 $s$ 记录黑板上当前有哪些元素;另一个可重集 $ans$ 记录 $s$ 中相邻元素的异或值。
插入删除操作在维护 $s$ 的同时维护一下 $ans$ 即可。
代码
#include <bits/stdc++.h>
#define L(i, a, b) for(int i = (a); i <= (b); i++)
#define R(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
const int N = 3e5 + 10;
int q;
multiset<int> s, ans;
int main(){
scanf("%d", &q);
while(q--){
int op, x; scanf("%d", &op);
if(op == 1){
scanf("%d", &x), s.insert(x);
if(s.size() > 1){
auto it = s.find(x), pre = it, nxt = it, it2 = s.end(); it2--;
if(it == s.begin()) nxt++, ans.insert((*nxt) ^ x);
else if(it2 == it) pre--, ans.insert((*pre) ^ x);
else{
pre--, nxt++, ans.erase(ans.find((*pre) ^ (*nxt)));
ans.insert((*pre) ^ x), ans.insert((*nxt) ^ x);
}
}
}
if(op == 2){
scanf("%d", &x);
if(s.size() > 1){
auto it = s.find(x), pre = it, nxt = it, it2 = s.end(); it2--;
if(it == s.begin()) nxt++, ans.erase(ans.find((*nxt) ^ x));
else if(it2 == it) pre--, ans.erase(ans.find((*pre) ^ x));
else{
pre--, nxt++, ans.insert((*pre) ^ (*nxt));
ans.erase(ans.find((*pre) ^ x)), ans.erase(ans.find((*nxt) ^ x));
}
}
s.erase(s.find(x));
}
if(op == 3) printf("%d\n", (*ans.begin()));
}
return 0;
}