1. 程式人生 > >集合選數

集合選數

集合選數

題目描述

 

《集合論與圖論》這門課程有一道作業題,要求同學們求出{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;