2020.02.13日常總結兼狀壓dp略講(下)

一道狀壓好題——洛谷P3226\color{green}{\text{一道狀壓好題——洛谷P3226}}

\color{blue}{【題意】:} 《集合論與圖論》這門課程有一道作業題,要求同學們求出{1,2,3,4,5}\{ 1, 2, 3, 4, 5 \}的所有滿足以 下條件的子集:若 xx 在該子集中,則 2x2x3x3x 不能在該子集中。

同學們不喜歡這種具有枚舉性質的題目,於是把它變成了以下問題:對於任意一個正整數 1n1×1051 \leq n \leq 1\times 10^5,如何求出{1,2,...,n}\{1, 2,..., n\} 的滿足上述約束條件的子集的個數(只需輸出對 1,000,000,001(1×109+1)1,000,000,001(1 \times 10^9+1) 取模的結果),現在這個問題就 交給你了。

\color{blue}{【思路】:} 一道構造+狀壓dp的好題目。

我們可以構造這麼一個矩陣,第11行第11個數是11,其後,每行的第jj個元素是該行第j1j-1個數的33倍,每列的第ii個數是該列的第i1i-1個數的22倍。比如這樣:

1  3  9  27  81...
2  6 18  54 162...
4 12 36 108 324...

然後,問題就變成了在這個矩陣中選數,且相鄰的數不能同時選的方案數。

考慮到其行數和列數不是很大,所以我們可以用狀壓dp輕鬆解決。

記第ii行的數取或不取的狀態用0,10,1表示,fi,jf_{i,j}表示第ii行狀態爲jj時的答案。轉移具體看代碼。

注意因爲從11開始構造矩陣不能把所有的數包含在內,需要把所有可能的矩陣構造出來,然後根據乘法原理,答案相乘即可。

\color{blue}{【代碼】:}

const ll mod=1000000001;
const int N=100100,M=20;
ll line[M],f[2][1<<18],n;
bool chose[N],g[(1<<18)+5];
int a[M][M],lim[M];ll ans=1ll,t;
inline void initialization(){
	for(int i=0;i<(1<<18);i++)
		if ((i&(i<<1))==0)
			g[i]=true;
		else g[i]=false;
}//預處理g數組(g[i]:i是否爲可行集合) 
inline void make_rectangle(int x){
	for(int i=1;i<12;i++){
		if (i==1) a[i][1]=x;
		else a[i][1]=a[i-1][1]*3;
		if (a[i][1]>n) break;
		t=i;chose[a[i][1]]=true;line[i]=1;
		for(int j=2;j<19;j++){
			a[i][j]=a[i][j-1]*2;
			if (a[i][j]>n) break;
			line[i]=j;chose[a[i][j]]=true;
		}
		lim[i]=(1<<line[i])-1;
	}
}//以x爲首個數字構造我們需要的矩陣 
inline ll dp(){
	register int i,j,k;
	for(i=0;i<=lim[1];i++)
		f[1][i]=g[i];
	for(i=2;i<=t;i++)
		for(j=0;j<=lim[i];j++)
			if (g[j]){
				f[i&1][j]=0ll;//不能用memset 
				for(k=0;k<=lim[i-1];k++)
					if (g[k]&&((j&k)==0))
						f[i&1][j]=(f[i&1][j]+f[(i&1)^1][k])%mod;
			}
	register ll res=0ll;
	for(i=0;i<=lim[t];i++)
		if (g[i]) res=(res+f[t&1][i])%mod;
	return res;
}//狀壓dp求解子問題 
int main(){
	scanf("%lld",&n);
	initialization();
	for(int i=1;i<=n;i++)
		if (!chose[i]){
			make_rectangle(i);
			ans=(ans*dp())%mod;
		}
	printf("%lld",ans);
	return 0;
}

再談狀壓\color{green}{\text{再談狀壓}}

  • 一般在數據範圍不大的dp題中,我們會優先地考慮狀壓dp
  • 如果狀態可以轉化爲一個東西取或不取,放或不放,就可以用二進制表示這個狀態,然後把它放入狀態的定義中,使用二進制運算符把這個問題變成狀壓dp求解的題目。
  • 二進制運算不是很好理解,在上一篇博客中我們講了二進制運算符,所以大家可以根據狀態的定義和二進制運算符的運算規矩畫圖理解。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章