集合选数

集合选数

题目描述

《集合论与图论》这门课程有一道作业题,要求同学们求出{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;
    

猜你喜欢

转载自blog.csdn.net/liankewei123456/article/details/84677885