狀態壓縮是設計dp狀態的一種方式。c++
當普通的dp狀態維數不少(或者說維數與輸入數據有關),但每一維總量不多時,能夠將多維狀態壓縮爲一維來記錄。spa
這種題目最明顯的特徵就是:都存在某一給定信息的範圍很是小(在20之內),而咱們在dp中所謂壓縮的就是這一信息。設計
(或者是在作題過程當中分析出了某一信息種類數不多)3d
咱們來看個例子。code
經典題blog
給出一個n*m的棋盤,要放上一些棋子,要求不能有任意兩個棋子相鄰。求方案數。it
n<=100;class
m<=8。二進制
若是m固定的話能夠設f[i][0/1][0/1]...[0/1]表示每一行每一列放不放im
若是不是固定的呢?
咱們發現後面的多個0/1能夠當作一個二進制數
那咱們不就能夠用數字代替後面的維數嗎?
f[i][s]->f[i+1][s’](s&s’==0)
你會發現這樣記錄很暴力,狀態數是與m相關的指數級的,但同時也就是由於m小咱們就確實能夠這麼作。
其實本質就是很暴力的記錄狀態,只不過利用了題目自己的特殊條件(這一維很小),使得咱們並不會所以複雜度太高。
同時也就是說,若是題目自己沒有這樣一個較小的信息,就不能應用狀態壓縮。
狀態壓縮dp確定是有一維是指數級的,這正是狀態壓縮的特色。
來看一道題:
P1896 [SCOI2005]互不侵犯
這個題能夠狀壓DP的很明顯的標誌就是數據範圍
咱們設f[i][j][k]表示當前在第i行,這一行及以前總共放了j個國王,當前的狀態是k
那麼咱們只要枚舉行,而後再枚舉狀態轉移就能夠了
怎麼判斷互不侵犯?
用位運算就能夠了
注意最後答案不能光統計最後一行,由於不必定在最後一行才用完全部的國王
代碼:
#include<bits/stdc++.h> using namespace std; typedef long long ll; int n,king; ll f[10][100][2000]; int s[2000],num[2000]; int cnt; ll ans; inline void pre() { int tot=(1<<n)-1; for(int i=0;i<=tot;i++) { if(!((i<<1)&i)) { s[++cnt]=i; int k=i; while(k) { num[cnt]+=k%2; k/=2; } } } } inline void dp() { for(int i=1;i<=cnt;i++) { if(num[i]<=king) f[1][num[i]][i]=1; } for(int i=2;i<=n;i++) { for(int j=1;j<=cnt;j++) { for(int k=1;k<=cnt;k++) { if((!(s[k]&s[j]))&&(!((s[k]<<1)&s[j]))&&(!(s[k]&(s[j]<<1)))) { for(int use=1;use<=king-num[j];use++) { f[i][num[j]+use][j]+=f[i-1][use][k]; } } } } } } int main() { scanf("%d%d",&n,&king); pre(); dp(); for(int i=1;i<=n;i++) { for(int j=1;j<=cnt;j++) { ans+=f[i][king][j]; } } cout<<ans; }