P3226 [HNOI2012]集合选数题解

· · 个人记录

博客

思路(状压DP)

  1. 题目要求“若 x 在该子集中,则 2x 和 3x 不能在该子集中”,那么我们就可以构造数个矩阵。

  2. 这些矩阵中没有重复的数,且保证了它们的并集取到了从1到n

  3. 其中每个矩阵都满足==对于矩阵中任意一个数,都满足它是它左边那个数的3倍,同时是上面那个数的2倍==,即a[i][j]=a[i-1][j]*2=a[i][j-1]*3,如图:

  4. 那么题目就变成了从所构造的矩阵当中取出若干个小于n的数的方法种数,就是典型的状压DP了。

  5. 由于有多个矩阵,最终答案即为各个矩阵所得到的答案之积

    小细节

  6. 行间是3倍,列间是2倍,这样可以减少每行的状态数,降低时间空间复杂度。

  7. 为了保证所有的矩阵中没有重复的数,每次构造矩阵都从未被用过的数中选取最小的一个作为矩阵的a[1][1]。

  8. 为了使取出的数满足小于等于n,可以通过判断状态所取到的最高位是否大于n,若大于n,则可直接退出(状态是单增的)。

  9. 但是构造矩阵是不满足小于等于n的 数也一样要计算出,确保上述判断有效。

  10. 注意开long long,==能模就模!==

    代码

#include<cstdio>
#include<cmath>
#define int long long
#define ri register int
const int maxn=1e5+9;
const int p=1e9+1;
int a[20][15],b[20][15];
int s[maxn],lg[maxn];
int dp[20][maxn];
int flag[maxn];
int n,ans=1;
void js(){
    int cnt=0;
    for(ri i=0;i<maxn;++i) 
    {
        if(a[1][lg[i]]>n) break;
        if(!(i&(i>>1))) s[++cnt]=i;
    }
    for(ri j=1;j<=cnt;++j) dp[1][j]=1;
    for(int i=2;i<=18;++i)
    {
        if(a[i][1]>n) break;
        for(int j=1;j<=cnt;++j)
        {
            if(a[i][lg[s[j]]]>n) break;
            for(int k=1;k<=cnt;++k)
            {

                if(!(s[j]&s[k])) dp[i][j]=(dp[i][j]+dp[i-1][k])%p;
                if(a[i-1][lg[s[k]]]>n) break;
            }
        }
    }
    int t=0;
    for(ri i=1;i<=18;++i)
    {
        if(a[i+1][1]<=n) continue;
        for(ri j=1;j<=cnt;++j)
            if(a[i][lg[s[j]]]<=n)
            t=(t+dp[i][j])%p;
        break;
    }
    if(t) ans=ans*t%p;
    for(ri i=1;i<=18;++i)
        for(ri j=1;j<=cnt;++j) 
            dp[i][j]=s[j]=0;
}
void get_a(int I){
    for(ri i=1;i<=18;++i)
        for(ri j=1;j<=12;++j)
        {
            a[i][j]=b[i][j]*I;
            if(a[i][j]<=n) flag[a[i][j]]=1;
        }
}
void solve(){
    scanf("%lld",&n);
    b[1][1]=1;
    for(ri i=2;i<=12;++i) b[1][i]=b[1][i-1]*3;
    for(ri i=1;i<=maxn;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    for(ri i=2;i<=18;++i)
        for(ri j=1;j<=12;++j)
            b[i][j]=b[i-1][j]*2;
    for(ri I=1;I<=n;++I)
    {
        if(flag[I]) continue;
        get_a(I);
        if(a[1][1]>n) break;
        js();
    }
    printf("%lld\n",ans);   
}
#undef int
int main(){
    solve();
    return 0;
}