【算法】线性基求异或和第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;
}
posted @ 2020-11-16 16:58  I11usi0ns  阅读(828)  评论(1编辑  收藏  举报