luogu-P3226题解

简要题意

对于任意一个正整数 \(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 \}\),并且把有直接影响的数连边,我们尝试在方格图上求解问题。

\[\begin{aligned} &1 & 2 && 4 && 8 && 16\\ &3 & 6 && 12\\ &9 & 18 \end{aligned} \]

此时约束条件等价于选方格,要求选中的方格不相邻的方案数。可以考虑枚举一维,状压一维,且方格图不大,所以可通过此题。

代码

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;
}
posted @ 2025-03-29 19:38  Lyrella  阅读(19)  评论(0)    收藏  举报