BZOJ_2734_[HNOI2012]集合选数_构造+状压DP
题意:《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
分析:
我们构造出一个矩阵
1 | 2^0*3^1 | 2^0*3^2 |
2^1*3^0 | 2^1*3^1 | 2^1*3^2 |
2^2*3^0 | 2^2*3^1 | 2^2*3^2 |
发现矩阵的相邻两个格子的数不能同时取
状压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); }