题解:P4869 albus就是要第一个出场

节选自:线性代数学习笔记(二):线性基

为什么没有人写简单易懂的二分答案。

先介绍一下如何在线性基中求第 \(k\) 大 / 小值。我们记 \(w_i\) 表示线性基中二进制位最高位为 \(i\) 的那个基。

我们考虑求第 \(k\) 小的困难在哪里,就是有些 \(w_i\) 会有好几个二进制位相同,异或时会互相干扰,有些时候两个基异或起来会变大,有些时候两个基异或起来会变小。因此不方便统计,如果我们能把基变成如下形式:

\[(100\dots000)_2 + x_1\\ \,\,\,(10\dots000)_2 + x_2\\ \,\,\,\,\,\,(1\dots000)_2 + x_3\\ \,\,\,\,\,\,\,\,\,\vdots\\ \,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,(100)_2 + x_{n - 2}\\ \,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,(10)_2 + x_{n - 1}\\ \,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,(1)_2 + x_n \]

(其中 \(x_i\) 也为二进制下的数,加号表示把两个二进制数拼在一起)

我们设最后化简出了 \(n\) 个基 \(w_1 < w_2 < w_3 < \dots < w_{n - 1} < w_n\),此时最小的能被异或出的数就变成了 \(w_1\),第二小是 \(w_2\),第三小是 \(w_1 \operatorname{xor} w_2\)(此时的 \(\operatorname{xor}\) 不会使两个基异或起来变小这),这时我们发现如果将 \(k\) 二进制表示成了 \((b_0b_1b_2\dots b_{x - 1}b_x)\),那么答案就是 \(\operatorname{xor}_{0 \leq i \leq x} 2^i [b_i = 1]\)

现在的问题就是要求出这样一组性质优良的基。其实我们可以发现,每个 \(w_i\) 都是所有线性基中 \(w_i\) 最小的,根据这个特性,可以贪心地变化这组线性基。

我们从小到大枚举先前求出的基 \(w_i\),再从大到小枚举 \(j > i\),此时若存在 \(w_i \operatorname{xor} w_j < w_i\),就直接异或掉。我们利用类似数学归纳法的方式证明其正确性。考虑到如果 \(w_i \operatorname{xor} w_j < w_j\),那么一定是 \(w_j\) 的某些位与 \(w_i\) 相同,如果 \(w_i\) 的最高位与 \(w_j\) 的这 \(1\) 位都是 \(1\),那么异或就将 \(1\) 消掉了,否则由于 \(w_i \operatorname{xor} (w_i \operatorname{xor} w_j) = w_j\),因此这个线性基并未发生变化,但是此时就只有 \(w_i\)\(i\) 位置有值了,因此我们就构造成功了。

其实,我们可以再简化一下,如果 \(j > i\)\(j\) 的第 \(i\) 位上都有 \(1\) 此时直接将 \(w_j \operatorname{xor} w_i\) 就可以了。

回到这道题目,我们只需要套一个二分答案就可以了,时间复杂度为 \(O(\log^2 D)\)\(D\) 为值域)。

注意到这道题目是一个多重集合,由于我们知道线性基中,不存在一组数,使他们的异或和为 \(0\),那么可以推出,不存在两组数,使它们的异或和相等。那么,假如有 \(tot\) 个数被插入了线性基中,那么插入失败的数就有 \(n - tot\) 个。由于这些数全部可以用线性基表示出来,因此可以用 \(0\) 来表示。由于多少个 \(0\) 异或上一个数还是这个数,因此其实每个数都出现了 \(2^{n - tot}\) 次,那么将最终答案乘以 \(2^{n - tot}\) 即可。

完整代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 59, MOD = 10086;
int n, m;
int w[N], cnt;
int qpow(int a, int b){
	int res = 1;
	while(b > 0){
		if(b & 1)
			res = res * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}
struct Basis{
	void insert(int x){
		for(int i = 55; i >= 0; --i){
			if((x >> i) & 1){
				if(w[i])
					x ^= w[i];
				else {	 
					w[i] = x;
					break;
				}
			}	
		}
	}
	void build(){
		for(int i = 55; i >= 0; i--){
			if(!w[i])
				continue;
			for(int j = i + 1; j <= 55; j++){
			    if((w[j] >> i) & 1)
			    	w[j] ^= w[i];
			}
		}
		for(int i = 0; i <= 55; i++){
			if(w[i])
				w[cnt++] = w[i];
		}	
		cnt--;
	}
	int query(int x){
		int res = 0;
		for(int i = cnt; i >= 0; i--)
			if((x >> i) & 1)
				res ^= w[i];
		return res;
	}
} b;
signed main(){
	scanf("%lld", &n);
	for(int i = 1; i <= n; i++){
		int x;
		scanf("%lld", &x);
		b.insert(x);
	}
	b.build();
	int x;
	scanf("%lld", &x);
	int l = 0, r = (1ll << (cnt + 1)) - 1;//枚举排名
	while(l < r){
		int mid = (l + r + 1) >> 1;
		if(b.query(mid) <= x)
			l = mid;
		else
			r = mid - 1;
	}
	printf("%lld", (qpow(2, n - cnt - 1) * l + 1) % MOD);
	return 0;
}
posted @ 2025-03-20 15:04  Orange_new  阅读(19)  评论(0)    收藏  举报