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 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
题解:
这题的思路蛮有趣的。
我们不妨写出来一个矩阵。
行\列 |
1 |
2 |
3 |
4 |
5 |
1 |
1 |
3 |
9 |
27 |
81 |
2 |
2 |
6 |
18 |
54 |
162 |
3 |
4 |
12 |
36 |
108 |
324 |
4 |
8 |
24 |
72 |
216 |
648 |
5 |
16 |
48 |
144 |
432 |
1296 |
该矩阵的每一个元素的右面的元素都是他的3倍,每一个元素的下面的元素都是他的2倍。
也就是说,如果我们选出的数在该矩阵中是不相邻的话,那么选出的一定是一个符合题意的子集。
因为n≤100000,所以该矩阵的行和列一定都很小,最多大概是在17。
所以我们可以考虑在这个矩阵上状压每一行,然后统计一下该矩阵可取的方案数即可。
但是我们发现,5,7这种数并没有出现在该矩阵中。
所以这种矩阵可能有多个,在找完1为左上角的该类型矩阵后,我们只需要寻找1~n中的下一个没有出现在矩阵中的元素充当左上角,再次计算即可。
由于不同矩阵中的元素互不影响,所以我们需要把所有可能的矩阵的方案数利用乘法原理乘在一起即可。
1 //Never forget why you start 2 #include<iostream> 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<cmath> 7 #include<algorithm> 8 #define mod (1000000001) 9 using namespace std; 10 typedef long long lol; 11 lol n,m,map[21][21],vis[100005],ans=1,cnt[21],t[21],f[21][5005]; 12 lol dp(lol x) { 13 lol i,j,k; 14 map[1][1]=x; 15 for(i=2; i<=20; i++) 16 if(map[i-1][1]*2<=n)map[i][1]=map[i-1][1]*2; 17 else map[i][1]=n+1; 18 for(i=1; i<=20; i++) 19 for(j=2; j<=20; j++) 20 if(map[i][j-1]*3<=n)map[i][j]=map[i][j-1]*3; 21 else map[i][j]=n+1; 22 for(i=1; i<=20; i++) { 23 cnt[i]=0; 24 for(j=1; j<=20; j++) { 25 vis[map[i][j]]=1; 26 if(map[i][j]<=n)cnt[i]+=t[j]; 27 } 28 } 29 for(i=1; i<=20; i++) 30 for(j=0; j<=cnt[i]; j++) 31 f[i][j]=0; 32 f[0][0]=1; 33 for(i=0; i<=20; i++) 34 for(j=0; j<=cnt[i]; j++) 35 if(f[i][j]) 36 for(k=0; k<=cnt[i+1]; k++) 37 if(!(j&k)&&(!(k&(k<<1)))) 38 f[i+1][k]=(f[i+1][k]+f[i][j])%mod; 39 return f[20][0]; 40 } 41 int main() { 42 lol i,j; 43 scanf("%lld",&n); 44 for(i=1; i<=20; i++)t[i]=1<<(i-1); 45 for(i=1; i<=n; i++) 46 if(!vis[i]) 47 ans=(ans*dp(i))%mod; 48 printf("%lld\n",ans); 49 return 0; 50 }