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

bzoj 2734 集合選數

Written with StackEdit.

Description

《集合論與圖論》這門課程有一道作業題,要求同學們求出\(\{1, 2, 3, 4, 5\}\)的所有滿足以 下條件的子集:若 \(x\) 在該子集中,則 \(2x\)\(3x\) 不能在該子集中。同學們不喜歡這種具有列舉性 質的題目,於是把它變成了以下問題:對於任意一個正整數 \(n\leq 100000\),如何求出\(\{1, 2,..., n\}\) 的滿足上述約束條件的子集的個數(只需輸出對 \(1000000001\) 取模的結果),現在這個問題就 交給你了。

Input

只有一行,其中有一個正整數 \(n\)\(30\%\)

的資料滿足 \(n\leq20\)

Output

僅包含一個正整數,表示\(\{1, 2,..., n\}\)有多少個滿足上述約束條件 的子集。

Sample Input

4

Sample Output

8

【樣例解釋】

\(8\) 個集合滿足要求,分別是\(\emptyset\)\(\{1\}\)\(\{1,4\}\)\(\{2\}\)\(\{2,3\}\)\(\{3\}\)\(\{3,4\}\)\(\{4\}\).

Solution

  • 神仙構造題.
  • 考慮構造這樣的一個矩陣
    \[\begin{pmatrix} 1 & 3 & 9 & 27 & ...\\ 2 & 6 & 18 & 54 & ...\\ 4 & 12 & 36 & 108 & ...\\ 8 & 24 & 72 & 216 & ...\\ ... & ... & ... & ... & ...\\ \end{pmatrix} \quad\]
  • 其中一個數是它上邊那個數的\(2\)倍,左邊那個數的\(3\)倍.
  • 原問題轉化為從矩陣中選出一些互不相鄰的數.
  • 那麼這個矩陣中的數是呈指數級增長的,規模不會超過\(20\).
  • 使用經典狀壓\(dp\)處理,逐行考慮.
  • 另,對於每個尚未出現過的數,需以它為左上角建一個矩陣,這樣各個構造出的矩陣互不相交,利用乘法原理統計答案.

    左上角的數增大時,矩陣沒有填入數字的部分也不斷增大.手動\(memset\).

#include<bits/stdc++.h>
using namespace std;
typedef long long LoveLive;
inline int read()
{
    int out=0,fh=1;
    char jp=getchar();
    while ((jp>'9'||jp<'0')&&jp!='-')
        jp=getchar();
    if (jp=='-')
        {
            fh=-1;
            jp=getchar();
        }
    while (jp>='0'&&jp<='9')
        {
            out=out*10+jp-'0';
            jp=getchar();
        }
    return out*fh;
}
const int P=1e9+1;
inline int add(int a,int b)
{
    return (a + b) % P;
}
inline int mul(int a,int b)
{
    return 1LL * a * b % P;
}
const int MAXN=1e5+10;
int n;
int vis[MAXN];
int Martix[20][20];
int limit[20];//第i行的邊界 
int ans=1;
int r,c;
int judge(int st)
{
    int ls=0;
    while(st)
        {
            int p=st&1;
            if(p && ls)
                return 0;
            ls=p;
            st>>=1;
        }
    return 1;
}
vector<int> G;
void Build_Martix(int x)
{
    memset(Martix,-1,sizeof Martix);
    r=0,c=0;
    int p;
    for(int i=1;i<20;++i)
        {
            p=i==1?x:Martix[i-1][1]*2;
            if(p>n)
                {
                    r=i-1;
                    break;
                }
            Martix[i][1]=p;
            vis[p]=1;
            for(int j=2;j<20;++j)
                {
                    p=Martix[i][j-1]*3;
                    if(p>n)
                        {
                            c=max(c,j-1);
                            limit[i]=j-1;
                            break;  
                        }   
                    Martix[i][j]=p;
                    vis[p]=1;
                }
        }
    G.clear();
    int S=1<<c;
    for(int i=0;i<S;++i)
        if(judge(i))
            G.push_back(i);
}
int f[20][1<<20];
inline int check(int x,int y)
{
    return !(x&y);
}
int dfs(int k,int st)//填好了前k行,且第k行狀態為st的方案數. 
{
    if(k==1)
        return 1;
    if(f[k][st]!=-1)
        return f[k][st];
    int &res=f[k][st];
    res=0;
    int S=(1<<limit[k-1])-1;
    for(int v=0;v<G.size();++v)
        {
            int i=G[v];
            if(i>S)
                break;
            if(check(i,st))
                res=add(res,dfs(k-1,i));    
        }
    return res;
}
void solve(int x)//以x為左上角構造矩陣的方案數 
{
    Build_Martix(x);
    for(int i=1;i<=r+1;++i)
        {
            int S=1<<limit[i];
            for(int j=0;j<S;++j)
                f[i][j]=-1;
        }
    //memset(f,-1,sizeof f);
    ans=mul(ans,dfs(r+1,0));
}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)
        if(!vis[i])
            solve(i);
    printf("%d\n",ans);
    return 0;
}