1. 程式人生 > >【NOI2001】 炮兵陣地 (狀壓dp)

【NOI2001】 炮兵陣地 (狀壓dp)

ron 很多 == 處理 gist i++ ostream push 計算

傳送門

Luogu

題目描述

司令部的將軍們打算在NM的網格地圖上部署他們的炮兵部隊。一個NM的地圖由N行M列組成,地圖的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下圖。在每一格平原地形上最多可以布置一支炮兵部隊(山地上不能夠部署炮兵部隊);一支炮兵部隊在地圖上的攻擊範圍如圖中黑色區域所示:

技術分享圖片

如果在地圖中的灰色所標識的平原上部署一支炮兵部隊,則圖中的黑色的網格表示它能夠攻擊到的區域:沿橫向左右各兩格,沿縱向上下各兩格。圖上其它白色網格均攻擊不到。從圖上可見炮兵的攻擊範圍不受地形的影響。 現在,將軍們規劃如何部署炮兵部隊,在防止誤傷的前提下(保證任何兩支炮兵部隊之間不能互相攻擊,即任何一支炮兵部隊都不在其他支炮兵部隊的攻擊範圍內),在整個地圖區域內最多能夠擺放多少我軍的炮兵部隊。

思路

首先可以看出這是一道狀壓dp,然後具體思考如何進行狀壓,首先可以想到用四進制表示離中心的位置,然後每一行向下掃,向下掃的時候利用四進制數進行狀態轉移,如11表示離中心距離為3,是可以放的,10表示2,10表示1,00表示中心,但是仔細想想會發現這種做法無論在時間,空間,還是代碼實現難度上都是難以實現的,所以就要換一種思路考慮。

多謝瑞屎爺的幫助,才過了這道題2333333

其實這道題用二進制就可以了,用1表示這個位置上放了炮兵。我們可以觀察到,每個炮兵的影響位置有兩個,所以我們要枚舉前兩行的狀態,我們一橫行一橫行向下掃,用1表示這個這個位置放了炮兵。

dp[i][j][k]表示當前選到了第i行,當前的狀態為j時,前一行的的狀態為k時的最大數量,所以有4重循環,由上一行枚舉到這一行。

註意

這道題如果直接寫狀壓的的話,狀態會非常多,數組會炸,空間也會炸O(∩_∩)O。我們發現,同一行至少間隔兩個格子才能放一個炮兵,所以允許的狀態要比原有的少很多,我們首先預處理出所有的狀態,發現只有不到100種,好像是88種╮(╯▽╰)╭

所以時間上不會爆炸233333

用vector數組記錄一下所有的情況。

在枚舉的時候,還要註意到以下幾點

1.要判斷每個狀態能否放置在地圖上

2.要判斷每個狀態的炮兵能否互相打到

3.用來轉移的前一種狀態是否存在

代碼

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4
#include<cstring> 5 #include<vector> 6 using namespace std; 7 vector<int>v; 8 int N,M; 9 bool map[200][200]; 10 int check[300]; 11 int dp[200][200][200]; 12 int val[300]; 13 inline bool judge (int a){ //判斷這種狀態自己行內的炮兵能否互相打到 14 if((a&(a<<1)))return false; 15 if((a&(a<<2)))return false; 16 else return true; 17 } 18 inline int calc(int x){ //計算每種情況的炮兵個數 19 int ans=0; 20 while(x){ 21 if(x&1)ans++; 22 x=x>>1; 23 } 24 return ans; 25 } 26 int main(){ 27 memset(dp,-1,sizeof(dp)); 28 cin>>N>>M; 29 int tot=(1<<M)-1; 30 int shu=0; 31 for(register int i=0;i<=tot;i++){ //預處理出所有可能的狀態並用vector數組記錄 32 if(judge(i)){ 33 v.push_back(i); //用vector數組記錄情況 34 val[shu]=calc(i); //用val數組記錄當前情況有多少個炮兵 35 shu++; 36 } 37 } 38 char temp; 39 for(register int i=1;i<=N;i++){ 40 for(register int j=1;j<=M;j++){ 41 cin>>temp; 42 if(temp==P)map[i][j]=1; 43 else map[i][j]=0; 44 check[i-1]+=(map[i][j]<<(j-1)); //生成地圖的二進制形式,如果為1,則可以放炮兵 45 } 46 } 47 int cnt=v.size()-1; 48 for(register int i=0;i<=cnt;i++){ 49 if((v[i]|check[0])==check[0]){ 50 dp[0][i][0]=val[i]; //初始化dp數組,第0列不受前面列的影響,因此每種狀態的初始值就是這種狀態的炮兵數量 51 } 52 } 53 for(register int i=1;i<N;i++){ //枚舉行 54 for(register int j=0;j<=cnt;j++){ // 枚舉當前狀態 55 if((v[j]|check[i])!=check[i])continue; //判斷當前的狀態能否在地圖上放置 56 for(register int l=0;l<=cnt;l++){ //枚舉上一行的狀態 57 if(v[l]&v[j])continue; //判斷前一行的炮兵能否打到當前行的炮兵 58 if((v[l]|check[i-1])!=check[i-1])continue; //判斷前一行的炮兵能否放在地圖上 59 for(register int ll=0;ll<=cnt;ll++){ // 枚舉上一行的上一行的狀態 60 if(dp[i-1][l][ll]==-1)continue; //判斷上一種情況是否存在 61 if(v[ll]&v[j])continue; //判斷兩行前的炮兵會不會達到當前行的炮兵 62 if(v[ll]&v[l])continue; //判斷兩行前的炮兵會不會達到上一行的炮兵 63 if((v[ll]|check[i-2])!=check[i-2])continue; //判斷兩行前的炮兵能否放置在地圖上 64 dp[i][j][l]=max(dp[i-1][l][ll]+val[j],dp[i][j][l]); //狀態轉移 65 } 66 } 67 } 68 } 69 int ans=0; 70 for(register int i=0;i<=cnt;i++){ 71 for(register int j=0;j<=cnt;j++){ 72 ans=max(dp[N-1][i][j],ans); //因為要求最大值,所以枚舉最後一行的所有狀態的人數,取最大值 73 } 74 } 75 cout<<ans<<endl; 76 }

【NOI2001】 炮兵陣地 (狀壓dp)