bzoj 2734: [HNOI2012]集合选数
题目描述
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。
同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n<=100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
输入输出格式
输入格式:
只有一行,其中有一个正整数 n,30%的数据满足 n<=20。
输出格式:
仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。
输入输出样例
输入样例#1:
4
输出样例#1:
8 【样例解释】 有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
题解:
这题很有意思,首先你得想到画出所有的倍数表,然后再发现规律......
如:
1 3 9
2 6 18
4 12 36
8 24 72
16 48 144
32 96 288
大概是这样横着是乘三,竖着乘二
这样画出来就发现题目要求的就是所选的数不能相邻......且行列都是log的所以可以直接状压dp
设f[i][j] 表示前i行,第j行状态为j 那么判断一下直接转移就是了
注意状压的要是乘三的,状态比乘二的少很多.
做到这样发现这个矩阵并没有包括所有的数字
所以还需要找到一个没出现的数继续构造矩阵并dp统计
如没出现的7
7 21 63.....
14 42 126...
继续构造即可
最后答案统计时相乘即可
1 #include <algorithm> 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #define RG register 8 #define il inline 9 using namespace std; 10 typedef long long ll; 11 const int N=1<<12,M=100005,mod=1000000001; 12 int f[25][N],lim,sz[N]; 13 bool vis[M]; 14 il int deal(int sta){ 15 int n=0,s=sta,t=sta; 16 for(int i=1;i<=20;i++){ 17 if(s>lim){ 18 n=i-1; 19 break; 20 } 21 vis[s]=true;t=s; 22 sz[i]=0; 23 for(int j=1;j<=15;j++){ 24 if(t>lim)break; 25 vis[t]=true; 26 sz[i]++; 27 t=(t<<1)+t; 28 } 29 s<<=1; 30 } 31 f[0][0]=1; 32 for(int i=1;i<=n;i++){ 33 int tot=(1<<sz[i])-1; 34 for(RG int j=0;j<=tot;j++){ 35 if((j<<1)&j)continue; 36 int tmp=(1<<sz[i-1])-1; 37 f[i][j]=0; 38 for(RG int k=0;k<=tmp;k++){ 39 if((k<<1)&k)continue; 40 if(j&k)continue; 41 f[i][j]+=f[i-1][k]; 42 if(f[i][j]>=mod)f[i][j]-=mod; 43 } 44 } 45 } 46 int tot=(1<<sz[n])-1,ret=0; 47 for(RG int j=0;j<=tot;j++){ 48 if(j&(j<<1))continue; 49 ret+=f[n][j];if(ret>=mod)ret-=mod; 50 } 51 return ret; 52 } 53 void work() 54 { 55 scanf("%d",&lim); 56 ll ans=1; 57 for(int i=1;i<=lim;i++){ 58 if(!vis[i]) 59 ans*=deal(i),ans%=mod; 60 } 61 printf("%lld\n",ans); 62 } 63 64 int main() 65 { 66 work(); 67 return 0; 68 }