线性基
插入线性基代码:
void add(int x){
for (int i = 30;i >= 0;i--){
if (x&(1 << i)){
if (d[i]) x ^= d[i];
else{
d[i] = x;break;
}
}
}
}
性质:
-
原序列里面的任意一个数都可以由线性基里面的一些数异或得到
-
线性基里面的任意一些数异或起来都不能得到0
-
线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
解决问题:
1、 如何求序列中异或和的最大值
首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为0
代码:
void add(int x){
for (int i = 30;i >= 0;i--){
if (x&(1 << i)){
if (d[i]) x ^= d[i];
else{
d[i] = x;break;
}
}
}
}
int getans(){
int res = 0;
for (int i = 30;i >= 0;i--){
if (res^d[i] > res) res ^= d[i];
}
return res;
}
为什么这样做正确呢? 高位为1的贡献一定比低位为1的贡献大
2、 如何求线性基内元素异或最小值
显然为最小的d[i]。
如果是求整个序列能异或出的最小值而不是这个序列的线性基能异或出的最小值的话,要另外看一看有没有元素不能插入线性基,如果有,那么最小值就是0,否则依然是最小的d[i]
3、 如何求第k小值
回想上面的线性基处理过程,可以发现,处理完之后,线性基中的元素,作用其实都是提供自己最高位上的1,那么只要使提供出来的1可以和k的每一位上的1对应,那么求出来的ans 一定就是第k小的。
补充:仔细想想就能知道,其实处理完之后的线性基其实也还是原序列的一个线性基,因为依然拥有上面的三个性质,要知道,一个序列的线性基不唯一,只是元素数量唯一而已。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int maxn = 1e4+10;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
int d[maxn],tot;
void add(int x){
for (int i = 60;i >= 0;i--){
if (x&(1ll << i)){
if (d[i]) x ^= d[i];
else{
d[i] = x,tot++;
break;
}
}
}
}
void init(){
for (int i = 1;i <= 60;i++){
for (int j = 1;j <= i;j++) if (d[i]&(1ll << (j-1))) d[i] ^= d[j-1];
}
}
int cas,T,n,a[maxn],q;
int kth(int k){
if (k == 1&&tot < n) return 0;
if (tot < n) k--;
int res = pow(2,tot);
// cout<<"res = "<<res<<endl;
if (pow(2,tot) <= k) return -1;
init();
int ans = 0;
for (int i = 0;i <= 60;i++){
if (d[i] != 0){
if (k%2 == 1) ans ^= d[i];
k /= 2;
}
}
return ans;
}
signed main(){
T = read();
while (T--){
cas++;
printf("Case #%lld:\n",cas);
n = read();memset(d,0,sizeof(d));tot = 0;
for (int i = 1;i <= n;i++) a[i] = read();
for (int i = 1;i <= n;i++) add(a[i]);
q = read();
while (q--){
int k = read();
printf("%lld\n",kth(k));
}
}
return 0;
}