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\n",r,c); } int r=0; for(int i=x;i<=n;i*=2,r++); //printf("dp %d %d \n",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\n",i,j,f[i][j]); } int _=0; for(int j=0;j<col[r];j++) mod(_+=f[r][j]); //printf("_ %d\n",_); 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); }
Copyright:http://www.cnblogs.com/candy99/