bzoj 2734 集合選數
阿新 • • 發佈:2018-11-21
Written with StackEdit.
Description
《集合論與圖論》這門課程有一道作業題,要求同學們求出\(\{1, 2, 3, 4, 5\}\)的所有滿足以 下條件的子集:若 \(x\) 在該子集中,則 \(2x\) 和 \(3x\) 不能在該子集中。同學們不喜歡這種具有列舉性 質的題目,於是把它變成了以下問題:對於任意一個正整數 \(n\leq 100000\),如何求出\(\{1, 2,..., n\}\) 的滿足上述約束條件的子集的個數(只需輸出對 \(1000000001\) 取模的結果),現在這個問題就 交給你了。
Input
只有一行,其中有一個正整數 \(n\),\(30\%\)
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; }