线性基
我们称 \(B\) 是集合 \(S\) 的线性基, 当且仅当 :
- \(S \subseteq \operatorname{span}(S)\) .
- \(B\) 线性无关.
这里不再解释线性相关 / 无关的概念, 有需求的读者可以自行翻阅线性代数。
其中, \(B\) 的元素个数称为线性基的长度。
线性基的性质
-
\(S\) 中的任意元素可以唯一表示由 \(B\) 中若干个元素异或起来的结果。
-
\(B\) 中不存在一个非空子集使得异或和为 \(0\)。
-
若选取 \(S\) 中任意子集异或, 则每个值会出现 \(2^{n - len}\) 次, 其中 \(len\) 为线性基大小。
例题 :
CF388D
可以发现一个完美集合是一个线性基异或而来再加上一个 \(0\)。
然而一个完美集合可以对应很多个线性基, 所以我们要想办法使它们一一对应。
回想我们用线性基求 \(k\) 大时, 我们对线性基进行了一次高斯消元, 目的是使线性基每一位独立。
相当于若第 \(i\) 位基存在, 那么其他基的第 \(i\) 位都应是 \(0\)。
结论 : 对于产生相同的完美集合的线性基, 它们按重构后的线性基是相同的。
也就是说, 这种线性基与完美集合一一对应。
还有一个重要的性质 : 这个线性基所有的基的异或之和, 恰好是线性基所能异或出的最大值。
然后这个题就是求最大值小于等于 \(n\) 的方案, 于是可以设计出一个 DP :
设 \(f_{i, j, k}\) 表示当前考虑 \(i\) 位, 之前已选 \(j\) 个基, 最大值是否卡到上界 \(k \in [0, 1]\)(类似数位 dp)
- \(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}\)。
- \(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 <bits/stdc++.h>
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;
}
``