【算法】线性基求异或和第k大
[题目链接](https://loj.ac/problem/114)
如果说,线性基中异或的最大数(的二进制形式)是一串 连续的,没有带后续0 的1,那相信聪明的你一定会求第k大,因为第k大其实就是k。
现在相当于告诉你在这一串1中夹了很多0,问你第k大是多少。那么你其实可以不用管中间的0,把k的二进制形式弄出来,然后把中间省略0给插回去就好了。
解释一下就相当于把线性基异或后出来的最大值里的所有1都给挤到最后,然后求出第k大,再把你弄走的0给丢回去。
eg:异或后最大值为101100110,问第20大
挤到后面去后成11111,第k大是10100,把0插回去成为100100000,第20大便是100100000
显然的证明:随便弄出两个数,把他们转成2进制,然后去掉非公共的0,发现两个数的相对大小关系不变。两个数的大小比较相当于在他们位数相同(或添上前导0后位数相同)的情况下比他们的字典序,而除去公共0后字典序的相对关系是不变的。
嗯就是这样,记得特判可不可以异或出0。
#include <bits/stdc++.h>
using namespace std;
int n,m,tot = 0;
long long a[1000005];
long long k[61],tt,x,cnt;
bool flag = 1;
void insert(long long x){
for (int i=60;i>=0;i--){
if (x&((long long)1LL<<i)) {
if (k[i]) x=x^k[i];
else {
tot++;
k[i]=x;
return ;
}
}
}
}
long long ask(long long x) {
if (tot < n && x == 1) return 0;
if (tot < n) x--;
if (x >= (1LL << (long long)cnt)) return -1;
long long ans = 0;
for (int i = 0;i <= 60;i++) {
if (k[i]) {
if (x % 2) ans ^= (long long)k[i];
x /= 2;
}
}
return ans;
}
int main(){
cin>>n;
for (int i=1;i<=n;i++) {
cin>>a[i];
insert(a[i]);
}
scanf("%d",&m);
for (int i = 0;i <= 60;i++) {
for (int j = 1;j <= i;j++) {
if (k[i] & ((long long)1LL << (long long)(j - 1))) k[i] ^= (long long)k[j - 1];
}
if (k[i]) cnt++;
}
for (int i = 1;i <= m;i++) {
long long x;
scanf("%lld",&x);
printf("%lld\n",ask(x));
}
return 0;
}