1. 程式人生 > 實用技巧 >狀態壓縮動態規劃(狀壓DP)詳解

狀態壓縮動態規劃(狀壓DP)詳解

0 引子

不要999,也不要888,只要288,只要288,狀壓DP帶回家。你買不了上當,買不了欺騙。它可以當搜尋,也可以卡常數,還可以裝B,方式多樣,隨心搭配,自由多變,一定符合你的口味!

在計算機裡,整數是以二進位制的方式儲存的。把狀態資訊壓縮成二進位制當成狀態進行動態規劃,就是狀壓DP的基本思想。

是不是一臉懵比?別急著關掉文章,接著往下看,你會發現一個新世界。

0.5 狀壓能解決什麼樣的問題?

讓我們康康這道題:傳送門

很容易想到搜尋,不是嗎?

然而,我們要用更裝比 複雜 優美的方式完成這道題。

1.什麼是“狀態壓縮?”

舉個例子吧。例如,例題裡的“取哪些砝碼”這個資訊,就可以壓縮排一個int整數裡。因為,int整數是二進位制儲存的。大概長這樣:

\[\]

十進位制 二進位制(也就是計算機裡實際的狀態)
1 1
2 10
114514 11011111101010010
6 110

我們可以發現,數字是由許多二進位制位構成的,要麼是0要麼是1。

是不是發現了什麼?

沒錯,我們可以用每一位的0和1表示每一個砝碼選或者不選!

例如,數字 2 (二進位制:10) 就可以表示1號砝碼不選,2號砝碼選的狀態。(因為第1位是0,第2位是1)。

知道了如何壓縮,就可以用動態規劃的方式求解了。

2.如何枚舉出每一個狀態?

事實上十分簡單,這樣就可以:

for(int i=0;i<=(1<<n)-1;i++){
}

(1<<n是把1左移n位的意思。例如,1<<3的二進位制就是1000,十進位制就是8)

我們來模擬一下\(n=3\)時的情況。

i i的二進位制
0 000
1 001
2 010
3 011
4 100
5 101
6 110
7 111

可以發現,i把所有砝碼選取情況都枚舉出來了。

3.如何轉移?

這個其實因題而異,我就用這道題舉例子吧

    for(int i=1;i<(1<<n);i++){//列舉。因為i=0就是一個也不選,所以不需要列舉。
        int high_bit=0,high_bit_num=0;
        for(int j=31;j>=0;j--){//int最多有31位,所以j=31-0。
            if((i>>j)&1){//意思是i的第j位是否為1
                high_bit=1<<j;
                high_bit_num=j;
                break;//找到了就退出來
            }
        }//求出當前方案的最高位
        sum[i]=sum[i^high_bit]+a[high_bit_num+1];//轉移。因為i是“按順序”列舉的,所以去掉最高位後的方案一定列舉過了。
        //i^high_bit的意思是去掉最高位
        cnt[i]=cnt[i^high_bit]+1;
    }

(這段程式碼求的是每種砝碼選取方案的重量和與每種砝碼選取方案要用多少砝碼)

註釋寫的很詳細,我就不再羅嗦了。

4.如果你想A掉這道題

你可以康康我的題解:傳送門

但是我還是建議你自己做出來

5.寫在最後

狀壓dp和其它所有演算法一樣,需要大量的練習才能掌握。

這篇文章寫的可能不怎麼全面,歡迎在評論區提問或指出。另外,不要吊死在一棵樹上,多康康別人的文章吧。