2734: [HNOI2012]集合选数
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1388 Solved: 820
[Submit][Status][Discuss]
Description
《集合论与图论》这门课程有一道作业题,要求同学们求出{1,
2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性
质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n}
的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
Input
只有一行,其中有一个正整数 n,30%的数据满足 n≤20。
Output
仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。
Sample Input
4
Sample Output
8
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
写出如下矩阵
1 3 9 27…
2 6 18 54…
4 12 36 108…
发现最多有11列,我们在其中选取一些数,相邻的不能选择,然后就可以状压求方案数了;
但是5没有出现,同样5的倍数也没有出现,7也如此……应该记录哪些数字出现过,没出现过就作为矩阵的第一个元素,最后把若干个矩阵的方案相乘即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const long long mod=1000000001; 7 long long ans; 8 int n; 9 int st[20],jz[20][20],g[20],f[20][5000]; 10 bool check[100010]; 11 int work(int x){ 12 memset(g,0,sizeof(g)); 13 jz[1][1]=x; 14 for(int i=2;i<=18;i++) 15 if(jz[i-1][1]*2<=n) jz[i][1]=jz[i-1][1]*2; 16 else jz[i][1]=n+1; 17 for(int i=1;i<=18;i++) 18 for(int j=2;j<=18;j++) 19 if(jz[i][j-1]*3<=n) jz[i][j]=jz[i][j-1]*3; 20 else jz[i][j]=n+1; 21 for(int i=1;i<=18;i++) 22 for(int j=1;j<=18;j++) 23 if(jz[i][j]<=n){ 24 check[jz[i][j]]=1; 25 g[i]+=st[j-1]; 26 } 27 for(int i=0;i<=18;i++) 28 for(int j=0;j<=g[i];j++) f[i][j]=0; 29 f[0][0]=1; 30 for(int i=0;i<18;i++) 31 for(int j=0;j<=g[i];j++) 32 if(f[i][j]) 33 for(int k=0;k<=g[i+1];k++) 34 if(!(j&k)&&!(k&(k>>1))) f[i+1][k]=(f[i][j]+f[i+1][k])%mod; 35 return f[18][0]; 36 } 37 int main(){ 38 scanf("%d",&n); 39 st[0]=1; 40 for(int i=1;i<=19;i++) st[i]=st[i-1]<<1; 41 ans=1; 42 for(int i=1;i<=n;i++) 43 if(!check[i]) ans=(ans*(long long)work(i))%mod; 44 printf("%lld",ans); 45 return 0; 46 }