BZOJ 2734: [HNOI2012]集合选数 [DP 状压 转化]

传送门

题意:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足若 x 在该子集中,则 2x 和 3x 不能在该子集中的子集的个数(只需输出对 1,000,000,001 取模的结果)


好巧妙的转化啊:

构造一个矩阵,把限制关系转化成矩阵的相邻元素不能同时选

1 3  9  27…

2 6 18 54…

4 12 36 108…

然后愉♂悦的状压DP就可以啦

注意每一个既不被$2$又不被$3$整除的数都可以作为矩阵的第一个元素,还有矩阵不一定填满

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=20,S=(1<<11)+5,P=1e9+1;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int n,f[N][S];
inline void mod(int &x){if(x>=P) x-=P;}
ll ans=1;
int col[N];
void dp(int x){
    for(int i=x,r=1;i<=n;i*=2,r++){
        int c=0;
        for(int j=i;j<=n;j*=3,c++);
        col[r]=1<<c;
        //printf("col %d %d
",r,c);
    }

    int r=0;
    for(int i=x;i<=n;i*=2,r++);
    //printf("dp %d %d 
",x,r);

    f[0][0]=1;col[0]=1;
    for(int i=1;i<=r;i++)
        for(int j=0;j<col[i];j++) if( (j&(j<<1))==0 ){
            f[i][j]=0;
            for(int k=0;k<col[i-1];k++) if( (j&k)==0 ) mod(f[i][j]+=f[i-1][k]);
            //printf("f %d %d %d
",i,j,f[i][j]);
        }
    int _=0;
    for(int j=0;j<col[r];j++) mod(_+=f[r][j]);
    //printf("_ %d
",_);
    ans=ans*_%P;
}
int main(){
    freopen("in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++) if(i%3 && i%2) dp(i);
    printf("%lld",ans);
}
原文地址:https://www.cnblogs.com/candy99/p/6512970.html