集合选数
集合选数
题目描述
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若x 在该子集中,则2x 和3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对1,000,000,001 取模的结果),现在这个问题就 交给你了。
输入
输入文件input.txt 只有一行,其中有一个正整数n,30%的数据满足n≤20。
输出
输出文件output.txt 仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。
样例输入
4
样例输出
8
提示
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
solution
神题,想不到
如果当前一个数为x,把x*2^p*3^q 都抓出来,当成一个集合
1x | 3x | 9x | 27x | 81x |
2x | 6x | 18x | . | . |
4x | 12x | . | . | . |
8x | . | . | . | . |
16x | . | . | . | . |
类似这样构出一个矩阵
不同的矩阵之间互不影响
那么我们肯定是要在矩阵中选若干个数,使得任意两列都不相邻
注意到列数少只有11 ,可以状压dp解决
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100005
#define ll long long
#define mod 1000000001
using namespace std;
int n,flag[maxn],Max[22];
ll ans=0,a[20][20],f[20][maxn];
ll work(int k){
int M;
for(int i=0;i<19;i++){
int t=(k*(1<<i));
if(t>n){M=i;break;}
for(int j=0,now=1;j<19;j++,now*=3){
a[i][j]=t*now;
//cout<<i<<' '<<j<<' '<<a[i][j]<<endl;
if(a[i][j]>n)break;
}
}
for(int i=0;i<M;i++){
Max[i]=0;
for(int j=0;j<19;j++){
if(a[i][j]>n)break;
if(!flag[a[i][j]])flag[a[i][j]]=1;
Max[i]+=(1<<j);
}
}
for(int i=0;i<=M;i++)
for(int j=0;j<=Max[i];j++)f[i][j]=0;
for(int j=0;j<=Max[0];j++)if(!(j&(j<<1)))f[0][j]=1;
for(int l=0;l<M;l++){
for(int i=0;i<=Max[l];i++){
for(int j=0;j<=Max[l+1];j++){
if((!(i&j))&&!(j&(j<<1))){
f[l+1][j]=(f[l+1][j]+f[l][i])%mod;
}
}
}
}
return f[M][0];
}
int main()
{
cin>>n;
ans=1;
for(int i=1;i<=n;i++)if(!flag[i])ans=(ans*work(i))%mod;
cout<<ans<<endl;