動態規劃【8】之狀態壓縮DP(2)

例題:luogu1896 [SCOI2005]互不侵犯

N×NN×N的棋盤裏面放KK個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各一個格子,共88個格子。
限制條件:N9,KN×NN\leq9, K\leq N×N

暴力

萬事都從暴力的方法說起, 考慮深度優先搜索,將在N×NN×N拿出KK個放國王,即C(N2,K)C(N^2,K)(排列組合)的時間複雜度(當然應該達不到)。最壞情況N=9N=9時超時。

優化

一個國王只能攻擊到他周圍的88個格子,也就是最後只能影響到33行(包括所在行)。第11行放置的國王只能影響到第11行和第22行。因此,我們可以一行一行放置國王,以減少枚舉狀態。

每一行看成一個狀態maskmask,由長度爲nn0/10/1串表示,11表示放了國王,00表示沒有放國王(0/10/1串見動態規劃之狀態壓縮DP中的描述)。如果maskmask由兩個相鄰的11是不合法的(對應行內相鄰位置放了兩個國王,後面將介紹二進制的運算);同時相鄰行的mask1mask1mask2mask2也要滿足互相不能攻擊到。

DP狀態及轉移式

我們一行一行放置國王,用dp[i][mask][k]dp[i][mask][k]表示放置了前ii行,第ii行國王的狀態爲maskmask,前ii行總共放了kk個國王的方案。
轉移式:dp[i][mask][k]={dp[i1][mask2][kp[mask]]},dp[i][mask][k]=\sum\{dp[i-1][mask2][k-p[mask]]\},
其中p[mask]p[mask]表示maskmask在二進制表示下11的個數,kp[mask]k\geq p[mask]mask2mask2本身要合法,同時maskmaskmask2mask2之間也要合法(國王不能互相攻擊到)。

初始化:dp[0][0][0]=1dp[0][0][0]=1, 其餘值爲00

輸出:mask=0mask=2n1dp[n][mask][K]\sum_{mask=0}^{mask=2^n-1} dp[n][mask][K],其中KK是總共要放的國王。

二進制表示簡化判斷

1、判斷maskmask有兩個相鄰的11: (mask&(mask>>1))>0(mask\&(mask>>1))>0;
2、判斷相鄰行的mask1mask1mask2mask2互相不能攻擊到:
(mask1&((mask2>>1)mask2(mask2<<1)))>0.(mask1\&((mask2>>1)|mask2|(mask2<<1)))>0.

代碼

感謝hyy提供的代碼:

#include <bits/stdc++.h>
using namespace std;

int n,m;
long long dp[10][1<<10][100];

int main(){
    cin >> n >> m;
    dp[0][0][0]=1;
    for(int i=1;i<=n;i++){
        for(int mask=0;mask<(1<<n);mask++){
            int num=0;
            for(int j=0;j<n;j++){
                if(mask&(1<<j))
                    num++;
            }
            bool flag0=(m>=num)&&((mask&(mask<<1))==0);
            if(flag0){
                
                for(int j=num;j<=m;j++){

                    for(int k=0;k<(1<<n);k++){
                        int num1=0;
                        for(int l=0;l<n;l++){
                            if(k&(1<<l))
                                num1++;
                        }
                        bool flag1=((k&(k<<1))==0)&&(num1+num<=j)&&((mask&k)==0)&&((mask&(k<<1))==0)&&((mask&(k>>1))==0);
                        if(flag1){
                            dp[i][mask][j]+=dp[i-1][k][j-num];
                        }
                    }
                }
            }
        }
    }
    long long ans=0;

    for(int i=0;i<(1<<n);i++){
        ans+=dp[n][i][m];
    }
    cout << ans;
    return 0;
}

狀態壓縮DP總結

一般來說,看到數據範圍比較小(n15n\leq 15),暴力搜索又不能過的時候,可以思考使用狀態壓縮;這是狀態壓縮的優點,同時也是它的缺點,只適用於數據範圍比較小的時候。


狀態壓縮題解,部分參考自https://oi-wiki.org/dp/state/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章