例題:luogu1896 [SCOI2005]互不侵犯
在的棋盤裏面放個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各一個格子,共個格子。
限制條件:。
暴力
萬事都從暴力的方法說起, 考慮深度優先搜索,將在拿出個放國王,即(排列組合)的時間複雜度(當然應該達不到)。最壞情況時超時。
優化
一個國王只能攻擊到他周圍的個格子,也就是最後只能影響到行(包括所在行)。第行放置的國王只能影響到第行和第行。因此,我們可以一行一行放置國王,以減少枚舉狀態。
每一行看成一個狀態,由長度爲的串表示,表示放了國王,表示沒有放國王(串見動態規劃之狀態壓縮DP中的描述)。如果由兩個相鄰的是不合法的(對應行內相鄰位置放了兩個國王,後面將介紹二進制的運算);同時相鄰行的和也要滿足互相不能攻擊到。
DP狀態及轉移式
我們一行一行放置國王,用表示放置了前行,第行國王的狀態爲,前行總共放了個國王的方案。
轉移式:
其中表示在二進制表示下的個數,,本身要合法,同時和之間也要合法(國王不能互相攻擊到)。
初始化:, 其餘值爲。
輸出:,其中是總共要放的國王。
二進制表示簡化判斷
1、判斷有兩個相鄰的: ;
2、判斷相鄰行的和互相不能攻擊到:
代碼
感謝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總結
一般來說,看到數據範圍比較小(),暴力搜索又不能過的時候,可以思考使用狀態壓縮;這是狀態壓縮的優點,同時也是它的缺點,只適用於數據範圍比較小的時候。
狀態壓縮題解,部分參考自https://oi-wiki.org/dp/state/