简要题意
对于任意一个正整数 \(n \le 10^5\),如何求出 \(\{1,2,\ldots ,n\}\) 的满足约束条件的子集的个数。约束条件:若 \(x\) 在该子集中,则 \(2x\) 和 \(3x\) 不能在该子集中。
数据范围:\(1 \le n \le 10^5\)。
题解
若我们将约束条件将互相有影响的数建成图,我们会惊喜地发现 \(n\) 个数会被划分到若干集合中,并且集合间独立。所以问题简化成只考虑一个集合的合法方案数。下面我们以一所在集合为例进行分析。
我们先列出集合 \(\{1,2,3,4,6,8,9,12,16,18\ldots \}\),并且把有直接影响的数连边,我们尝试在方格图上求解问题。
1 | 2 | 4 | 8 | 16 |
---|---|---|---|---|
3 | 6 | 12 | ||
9 | 18 |
此时约束条件等价于选方格,要求选中的方格不相邻的方案数。可以考虑枚举一维,状压一维,且方格图不大,所以可通过此题。
bool chk(int x, int y){return ! (x & y);}void solve(int st){res = 0;for(int i = 1, j; ; ++i){a[i][1] = i ^ 1 ? a[i - 1][1] * 3 : st;if(a[i][1] > n)break; m = i;vs.set(a[i][1]);for(j = 2; ; ++j){a[i][j] = a[i][j - 1] * 2;if(a[i][j] > n)break;vs.set(a[i][j]);}lim[i] = j - 1;}for(int i = 0; i < (1 << lim[1]); ++i)f[1][i] = g[i];for(int i = 2; i <= m; ++i)for(int j = 0; j < (1 << lim[i - 1]); ++j)if(g[j])for(int k = 0; k < (1 << lim[i]); ++k)if(g[k] and chk(j, k))(f[i][k] += f[i - 1][j]) %= p;for(int i = 0; i < (1 << lim[m]); ++i)(res += f[m][i]) %= p;for(int i = 2; i <= m; ++i)for(int j = 0; j < (1 << lim[i]); ++j)f[i][j] = 0;
}