演算法學習之路|狀態壓縮dp

kissjz發表於2018-02-11

動態規劃的題目中,狀態的表示是解題的關鍵,。在一些dp問題中,狀態可能會非常複雜,導致用來儲存狀態的dp陣列會有很多維。為此,我們需要用過狀態壓縮來達到減少狀態維數的目的。在狀態壓縮dp中,靈活運用位運算是一項必不可少的要求。

下面舉個簡單的例子說明怎樣縮減dp陣列維數:

有一個n*m的棋盤,上面放著一些棋子,如果用1表示座標為(i.j)的位置有棋子,0表示沒有。按照正常思路,我們會用一個二維陣列ai儲存每一個格子的資訊。但是此時我們可以看到,這個二維陣列是由0和1組成的,每一行(或列)都可以看成一個二進位制數。因此,我們可以用一個一維陣列儲存每一行(或列)的和棋子資訊等價的二進位制數,這就達到了縮減維數的目的。

一道例題:有一個mn的棋盤,在上面放12的小方塊,要鋪滿整個棋盤,求有多少種鋪法。(由於找不到原題,不清楚能否ac)
詳見程式碼

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#define mod 1000000007
using namespace std;
int dp[34][10005];
//dp[i][state]表示該填充第i行,第i-1行對它的影響是state的時候的方法數
int n,m;
void dfs(int i,int j,int pre,int nex)//i為進行到第i行,j表示進行到第i行的第j個,pre表示上一行對這一行的影響,nex表示這一行對下一行的影響
{
    if(j==n)
    {
        dp[i+1][nex]+=dp[i][pre];//當前行查詢完畢時,更新dp陣列的值,即表示i+1行狀態的個數是第i行狀態數的總和,此句是dp陣列更新的關鍵
        return;
    }
    //如果當前位置已經被上一行佔用
    if(((1<<(n-j-1))&pre)>0)//位運算的體現
        dfs(i,j+1,pre,nex);
    //如果當前位置空下,嘗試豎放方塊
    if(((1<<(n-j-1))&pre)==0)
        dfs(i,j+1,pre,nex|(1<<(n-j-1)));//把nex中第n-j位變為1
    //如果當前位為空且後一位也空,橫放方塊
    if(j+1<n&&((1<<(n-j-1))&pre)==0&&((1<<(n-j))&pre)==0)
        dfs(i,j+2,pre,nex);
    //以當前位為基準時,1*2小方塊的放法只有橫放在右邊或者豎放在下邊,因為往上和往左的情況已經被之前的搜尋找到過了
    return;
}
int main()
{
    while (cin>>n>>m)
    {
        memset(dp,0,sizeof(dp));
        if (n==0&&m==0) break;
        dp[1][0]=1;//一開始自然自有一種狀態且不受上一行影響
        for (int i=1;i<=m;i++)
        {
            for (int j=0;j<(1<<n);j++)
            if (dp[i][j])//如果此種狀態可行
            {
                dfs(i,0,j,0);
            }
        }
        cout<<dp[m+1][0]<<endl;
    }
}


相關文章