BZOJ_2734_[HNOI2012]集合选数_构造+状压DP

BZOJ_2734_[HNOI2012]集合选数_构造+状压DP

题意:《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。

分析:

我们构造出一个矩阵

$\begin{matrix}
    1&2^03^1&2^03^2\\
    2^13^0&2^13^1&2^13^2\\
    2^23^0&2^23^1&2^23^2\\
\end{matrix}
$

发现矩阵的相邻两个格子的数不能同时取

状压DP一下

要把所有不在矩阵中的数当作1重新构造,比如5,7等等

每个矩阵的结果乘起来就是答案

 

代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL long long
LL p=1000000001,A,f[18][1<<12];
int vis[100050],s[18],mat[18][18];
LL ans=1;
void build(int x){
    int n=1,m=1,now=x;
    while(now*3<=A)m++,now*=3;
    now=x;
    while(now*2<=A)n++,now<<=1;
    int mask=(1<<m)-1;
    memset(s,0,sizeof(s));
    memset(f,0,sizeof(f));
    memset(mat,0,sizeof(mat));
    mat[1][1]=x;vis[x]=1;
    for(int i=2;i<=m;i++){
        mat[1][i]=mat[1][i-1]*3;
        vis[mat[1][i]]=1;
    }
    s[1]=mask;
    for(int i=2;i<=n;i++){
        mat[i][1]=mat[i-1][1]*2;
        vis[mat[i][1]]=1;
        for(int j=2;j<=m;j++){
            mat[i][j]=mat[i-1][j]*2;
            if(mat[i][j]>A){
                s[i]=mask^((1<<m-j+1)-1);
                break;
            }
            vis[mat[i][j]]=1;
        }
        if(!s[i])s[i]=mask;
    }
    f[0][0]=1;
    s[0]=mask;
    for(int i=0;i<n;i++){
        for(int j=0;j<=mask;j++){
            if((j|s[i])!=s[i])continue;
            if(j&(j<<1))continue;
            for(int k=0;k<=mask;k++){
                if((k|s[i+1])!=s[i+1])continue;
                if(k&(k<<1))continue;
                if(j&k)continue;
                f[i+1][k]+=f[i][j];
                f[i+1][k]%=p;
            }
        }
    }
    LL re=0;
    for(int i=0;i<=mask;i++)re+=f[n][i],re%=p;
    ans=re*ans%p;
 
    /*for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            printf("%d ",mat[i][j]);
        }
        puts("");
    }*/
 
    /*for(int i=1;i<=n;i++){
        printf("%d\n",s[i]);
    }*/
}
int main(){
    scanf("%lld",&A);   
    for(int i=1;i<=A;i++){
        if(!vis[i])build(i);
    }
    printf("%lld",ans);
}

 

posted @ 2018-03-04 22:50  fcwww  阅读(182)  评论(0编辑  收藏  举报