在動態規划的題型中,一般叫什么DP就是怎么DP,狀壓DP也不例外
所謂狀態壓縮,一般是通過用01串表示狀態,充分利用二進制數的特性,簡化計算難度。舉個例子,在棋盤上擺放棋子的題目中,我們可以用1表示當前位置擺放棋子,用0表示當前位置不擺放棋子。
這樣的話,就能夠直接運用許多二進制運算的特性來實現對時間和空間的優化
例如:如果給你一個\(n*m\)的棋盤,讓你放棋子,但是棋子兩兩不能相鄰,求方案數
我們僅考慮暴力枚舉每一行的情況,如果是普通用數組來存儲,判斷的時候對於相鄰兩行需要一個數一個數的看,不論是時間還是空間,都很難讓人接受
但是如果用一個二進制數來存儲每一行的情況,對於相鄰兩行,我們只需要進行一下與運算,如果有值,就說明不合法,這樣不論是時間還是空間上,都進行了極大的優化
當然,這並不是說狀壓DP就是暴力枚舉,這里只是舉個例子來解釋一下狀壓DP中的狀態壓縮這個過程
看完上面這些,還是不知道這是個什么東西,所以我們結合一道題目來演示一下,什么是狀壓DP
這是一道非常經典的狀壓例題,題意過於簡單,我們不再贅述
我們首先考慮一下國王的特性:能夠攻擊到八個方向的棋子。也就是說,如果某個位置放置了棋子,它的九宮格內就不能再放置了。
我們可以將問題拆解,把九宮格看為三行,就可以分開考慮了
首先我們先考慮在當前行的棋子,只要一個棋子的左右沒有相鄰的棋子,那么就不沖突
再考慮上面那一行,只要一個棋子正上即左右側上沒有棋子則合法
最后再考慮一下下面那一行是否滿足條件
那么我們只要枚舉所有情況,再判斷就可以了
顯然,這樣做復雜度實在太高了,9*9的棋盤計算到人類滅亡連枚舉都枚舉不完
看來我們需要把算法優化一下
我們考慮可不可以逐行枚舉,先對每行的狀態進行判斷,在對上面一行和下面一行進行判斷。我們會發現這樣的做法是可行的,而且判斷下一行是沒有意義的,因為我們是逐行枚舉,下一行還沒枚舉到有什么好枚舉的。那么我們又會發現,這不是遞推嘛。可是這樣還是有點慢,怎么優化呢?
下面就是狀壓時刻了
我們可以在一開始將所有行內可行狀態存儲下來,由於可行狀態遠遠小於總的狀態數,這樣可以大大優化我們的復雜度。如何判斷這種狀態是否沒有沖突呢?用與運算就可以輕松判斷。我們先預處理好第一行的情況,然后遞推即可。
下面來看一下代碼吧(內附詳解)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 10
#define maxm 105 //由於可行狀態占比極小,所以行內的可行狀態不到100
using namespace std;
inline ll read(){
ll a=0;int f=0;char p=gc();
while(!isdigit(p)){f|=p=='-';p=gc();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
return f?-a:a;
}
int get(int x){ //求當前狀態中1的數量
int sum=0;
while(x){
++sum;
x&=x-1; //初賽考了,你做對了嗎
}
return sum;
}
int n,m,N,tot,s[maxm],w[maxm]; //s[i]表示第i種可行狀態的具體擺放,w[i]表示第i種狀態棋子的個數
ll ans,f[maxn][maxm][maxm]; //ans最終用來統計答案,f[i][j][l]表示第i行狀態為第j種擺放l個棋子的方案數
int main(){
n=read();m=read();N=1<<n;
for(int i=0;i<N;++i){
if(i&i<<1)continue; //如果非零說明有相鄰棋子,則不合法
s[++tot]=i;
w[tot]=get(i);
f[1][tot][w[tot]]=1; //第一行需要單獨初始化
}
for(int i=2;i<=n;++i)
for(int j=1;j<=tot;++j)
for(int k=1;k<=tot;++k){
if(s[j]&s[k])continue; //三種與上一行有沖突的情況
if(s[j]&s[k]<<1)continue;
if(s[k]&s[j]<<1)continue;
for(int l=w[j];l<=m;++l) //這里非常重要!!l的取值影響到f數組的意義,l如果是從w[j]開始,那么f數組表示的是這一行及以前所有放l個棋子的方案數;如果l從w[j]+1開始,那么f數組表示的是到這一行正好有l個棋子的方案數,以前夠了l個棋子的方案則沒有被記錄在這里面。這會影響到最后統計答案的,並且樣例太水並不能看出來。這里一定要注意!!!
f[i][j][l]+=f[i-1][k][l-w[j]];
}
for(int i=1;i<=tot;++i) //統計答案就很簡單了。不過如果是l是另一種寫法還需要枚舉1~n
ans+=f[n][i][m];
printf("%lld",ans);
return 0;
}
這就是狀壓DP,你是否理解了呢?不妨自己打一遍代碼,能夠加深理解哦。
例題
染色(一道我認為可用四進制狀壓解決的題目,如果有神仙成功了,希望能夠告訴我一聲,以證明我沒口胡錯)
茫茫人海相遇也算緣分,點個推薦好不好\(QwQ\)