线性基


我们称 \(B\) 是集合 \(S\) 的线性基, 当且仅当 :

  1. \(S \subseteq \operatorname{span}(S)\) .
  2. \(B\) 线性无关.

这里不再解释线性相关 / 无关的概念, 有需求的读者可以自行翻阅线性代数。

其中, \(B\) 的元素个数称为线性基的长度。

线性基的性质

  1. \(S\) 中的任意元素可以唯一表示\(B\) 中若干个元素异或起来的结果。

  2. \(B\) 中不存在一个非空子集使得异或和为 \(0\)

  3. 若选取 \(S\) 中任意子集异或, 则每个值会出现 \(2^{n - len}\) 次, 其中 \(len\) 为线性基大小。

例题 :
CF388D

可以发现一个完美集合是一个线性基异或而来再加上一个 \(0\)

然而一个完美集合可以对应很多个线性基, 所以我们要想办法使它们一一对应。

回想我们用线性基求 \(k\) 大时, 我们对线性基进行了一次高斯消元, 目的是使线性基每一位独立。

相当于若第 \(i\) 位基存在, 那么其他基的第 \(i\) 位都应是 \(0\)

结论 : 对于产生相同的完美集合的线性基, 它们按重构后的线性基是相同的。

也就是说, 这种线性基与完美集合一一对应。

还有一个重要的性质 : 这个线性基所有的基的异或之和, 恰好是线性基所能异或出的最大值。

然后这个题就是求最大值小于等于 \(n\) 的方案, 于是可以设计出一个 DP :

\(f_{i, j, k}\) 表示当前考虑 \(i\) 位, 之前已选 \(j\) 个基, 最大值是否卡到上界 \(k \in [0, 1]\)(类似数位 dp)

  1. \(k=0\), 即没卡到上界

不选 \(i\) : 之前 \(j\) 个的基的第 \(i\) 位任选, \(f_{i - 1, j + 1, 0} += f_{i, j, 0} \times 2^j\)

\(i\) : 第 \(i\) 位必定从 \(10..\) 开始, 且之前的 \(j\) 个的 \(i\) 位都要为 \(0\)\(f_{i - 1, j, 0} += f_{i, j, 0}\)

  1. \(k = 1\), 在上界

\(n\) 此位为 \(0\), 这位也为 \(0\), 并且之前 \(j\) 个基要有偶数个第 \(i\) 位为 \(0\) (第 \(i\) 位异或为 \(0\)

\(\sum\limits_{i=0}^{n}\dbinom{n}{2i} = 2^{n - 1}\)\(f_{i-1,j,1} += f_{i, j, 1} \times 2^{j - 1}\)

\(n\) 此位为 \(1\), 分 \(i\) 位是否选 :

? 选 : \(f_{i - 1, j + 1, 1} += f_{i, j, 1}\)

? 不选 : 若前 \(j\) 位选偶数个, 就不卡满 : \(f_{i - 1, j + 1, 0} += f_{i, j, 1} \times 2^{j - 1}\)

? 若前 \(j\) 位选奇数个, 也就是 \(\sum\limits_{i=0}^{n} \dbinom {n}{2i + 1} = 2^{n - 1}\)\(f_{i - 1, j + 1, 1} += f_{i, j, 1} \times 2^{j - 1}\)

? 注意特判选 \(0\) 个时, 此时选偶数个方案为 \(1\), 奇数为 \(0\)

#include 

const int INF = 0x3f3f3f3f;
const int M = 1e5 + 5;
const int mod = 1e9 + 7; 

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

//char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
//#define getchar() (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2)? EOF : *p1++

int n, power[31]; 
int f[31][31][2]; // 从大到小考虑到第 i 位, 已经选了 j 个基, 是否卡满上界 

inline void add(int &x, int y) {x += y; x >= mod ? x -= mod : x;}

inline int read() {
	int f = 1, s = 0;
	char ch = getchar();
	while(!isdigit(ch)) (ch == '-') && (f = -1), ch = getchar();
	while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
	return f * s;
}

int main()
{
	n = read(); 
	power[0] = 1; for(int i = 1; i <= 30; ++i) power[i] = 1ll * power[i - 1] * 2 % mod; 
	f[30][0][1] = 1; 
	for(int i = 30; i >= 1; --i) {
		for(int j = 0; j <= 30; ++j) {
			add(f[i - 1][j + 1][0], f[i][j][0]), add(f[i - 1][j][0], 1ll * f[i][j][0] * power[j] % mod); 
			int even = j ? power[j - 1] : 1, odd = j ? power[j - 1] : 0; 
			if((n >> i - 1) & 1) add(f[i - 1][j + 1][1], f[i][j][1]), add(f[i - 1][j][0], 1ll * even * f[i][j][1] % mod), add(f[i - 1][j][1], 1ll * odd * f[i][j][1] % mod); 
			else add(f[i - 1][j][1], 1ll * even * f[i][j][1] % mod); 
		}
	}
	int res = 0; 
	for(int i = 0; i <= 30; ++i) res = (res + f[0][i][0]) % mod, res = (res + f[0][i][1]) % mod; 
	std :: cout << res; 
	return 0;
}

``