题目大意 :
定义一个完美的集合 \(S\) ,当且仅当 \(S\) 非负非空,且 \(\forall a, b \in S, a\text{ xor } b \in S\) ,求集合内最大元素不超过 \(n\) 的完美集合数量\(1 \leq n \leq 10^9\)
解题思路 :
一个完美的集合等价于某个线性基能表示出的所有元素,所以问题转化为对能表示的最大元素 \(\leq n\) 的线性基计数
考虑一个集合的线性基可能有很多种,但是必然存在一种方案使得基上的二进制位表示出来的元素是最大元素,不妨直接对满足这种特征的线性基计数
考虑某一个二进制位如果在基中,那么基中其他位的数都不能有这个位,反之基中所有比它高的位都的数都可以有这个位
如果不考虑 \(n\) 的限制的话,可以得出一个简单的 \(dp[i][j]\) 表示从高到低第 \(i\) 个二进制位,已经选了 \(j\) 个二进制位作为基的方案数
根据上面推导可以得到 \(dp[i][j] = dp[i-1][j] \times 2^j + dp[i-1][j-1]\) (选这位为基/不选)
实际上如果有 \(n\) 的限制的话,是要满足有 \(1\) 的二进制位组成的二进制数不能超过 \(n\) ,可以直接套一个数位 \(dp\)
具体的说,\(dp[i][j][0/1]\) 表示从高到低第 \(i\) 个二进制位,已经选了 \(j\) 个二进制位作为基,当且有 \(1\) 的位是否是 \(n\) 在二进制下的前缀
根据当前 \(n\) 的二进制位是 \(0\) 还是 \(1\) 分讨转移即可
/*program by mangoyang*/#include#pragma GCC optimize("Ofast")#pragma GCC optimize("inline")#define inf (0x7f7f7f7f)#define Max(a, b) ((a) > (b) ? (a) : (b))#define Min(a, b) ((a) < (b) ? (a) : (b))typedef long long ll;using namespace std;template inline void read(T &x){ int f = 0, ch = 0; x = 0; for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; if(f) x = -x;}#define int llconst int Mod = 1e9+7;int s[55], f[55][55][2], len, ans; inline void up(int &x, int y){ x = (x + y % Mod) % Mod; }signed main(){ int x; read(x); if(!x) s[++len] = 0; while(x) s[++len] = x % 2, x /= 2; reverse(s + 1, s + len + 1); f[0][0][1] = 1; for(int i = 1; i <= len; i++) for(int j = 0; j <= len; j++){ if(j) up(f[i][j][0], f[i-1][j-1][0]); up(f[i][j][0], f[i-1][j][0] * (1ll << j)); int x = (j ? (1ll << j - 1) : 1) % Mod; int y = (j ? (1ll << j - 1) : 0) % Mod; if(s[i] == 0) up(f[i][j][1], f[i-1][j][1] * x); else{ if(j) up(f[i][j][1], f[i-1][j-1][1]); up(f[i][j][1], f[i-1][j][1] * y); up(f[i][j][0], f[i-1][j][1] * x); } } for(int i = 0; i <= len; i++) up(ans, f[len][i][0]), up(ans, f[len][i][1]); cout << ans; return 0;}