問題來源:POJ-1185
Description
司令部的將軍們打算在N*M的網格地圖上部署他們的炮兵部隊。一個N*M的地圖由N行M列組成,地圖的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下圖。在每一格平原地形上最多可以佈置一支炮兵部隊(山地上不能夠部署炮兵部隊);一支炮兵部隊在地圖上的攻擊範圍如圖中黑色區域所示:
如果在地圖中的灰色所標識的平原上部署一支炮兵部隊,則圖中的黑色的網格表示它能夠攻擊到的區域:沿橫向左右各兩格,沿縱向上下各兩格。圖上其它白色網格均攻擊不到。從圖上可見炮兵的攻擊範圍不受地形的影響。
現在,將軍們規劃如何部署炮兵部隊,在防止誤傷的前提下(保證任何兩支炮兵部隊之間不能互相攻擊,即任何一支炮兵部隊都不在其他支炮兵部隊的攻擊範圍內),在整個地圖區域內最多能夠擺放多少我軍的炮兵部隊。
Input
第一行包含兩個由空格分割開的正整數,分別表示N和M;
接下來的N行,每一行含有連續的M個字符('P'或者'H'),中間沒有空格。按順序表示地圖中每一行的數據。N <= 100;M <= 10。
Output
僅一行,包含一個整數K,表示最多能擺放的炮兵部隊的數量。
Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
Sample Output
6
先考慮這樣一個簡單的問題以便來分配數組的最大值(即10位情況下,布兵情況最大值),即1和1之間的間隔相差2以上的所有情況數:
<一>採用DFS深度優先搜索的策略求:
#include<iostream>
using namespace std;
int a[15] , ans=1; //若把每個點都不布兵看做一種情況的話,就從1開始
void dfs( int now );
int main( ){
dfs( 2 ); //規定布兵點標號從2到11(10個點),保證dfs中的i-2判斷不會越界
cout<<ans; //最多可能情況
return 0;
}
void dfs( int now ){
for( int i=now ; i<=11 ; i++ ){
if( !a[i-1] && !a[i-2] ){ //沒有部署衝突
a[i] = 1; //該位置部署炮兵
ans++; //可能的情況累加
dfs( i + 1 ); //繼續搜索
a[i] = 0; //撤銷布兵部署
}
}
}
<二>採用位機制求(二進制方法來解決,代碼簡潔,便於理解和調試):
#include<iostream>
using namespace std;
int main( ){
int i , sum=0;
for( int i=0 ; i<=(1<<10)-1 ; i++ ){ //遍歷所有的情況,二進制表示從000..000到111...111
if( (i&(i<<1)) || (i&(i<<2)) ) //移位運算,有重合的1,即不可以布兵
continue;
sum++; //可以布兵,sum累加
}
cout<<sum<<endl;
return 0;
}
輸出答案是60,所以在寫代碼之前可以將下方代碼的dp[MAX][MAX][MAX]改成dp[MAX][60][60],節省了部分空間.
囉嗦了半天,還是歸根到底來求最大布兵數吧:
源代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX = 102;
const int INF = 1<<30;
int N , M , sum;
int dp[MAX][MAX][MAX];
int stack[(1<<11)] , num[(1<<11)] , sit[MAX];
//dp[i][j][k]表示的含義是第i行數據取j這個狀態,i-1行數據取k這個狀態的最大布兵數
//stack保存可以布兵的所有情況,即上述所求的60種可行狀態
//num[k]是k這種狀態對應的二進制有多少個1
//sit[i]存儲每行地勢情況,平底(可以布兵)爲0,便於位運算
void Init( void );
int countNum( void );
int ok( int x , int y );
int max( int a , int b ){ return a >= b ? a : b; }
int main( ){
char ch;
while( cin>>N>>M ){
Init( );
getchar( ); //吞回車
for( int i=1 ; i<=N ; i++ ){
for( int j=1 ; j<=M ; j++ ){
cin>>ch;
if( ch=='H' ){
sit[i] += ( 1<<(j-1) ); //平底的位置可以布兵,標記爲0
}
}
getchar( ); //吞回車
}
cout<<countNum()<<endl;
}
return 0;
}
void Init( void ){
sum = 0;
memset( dp , 0 , sizeof( dp ) );
memset( stack , 0 , sizeof( stack ) );
memset( sit , 0 , sizeof( sit ) );
memset( num , 0 , sizeof( num ) );
for( int i=0 ; i<=(1<<M)-1 ; i++ ){
if( (i&(i<<1)) || (i&(i<<2)) ) //有重合的1情況,略去
continue;
stack[sum] = i;
int temp=i , s=0;
while( temp ){ //求二進制中1的個數
s++;
temp &= ( temp - 1 ); //抹去二進制中最後一個1
}
num[sum++] = s;
}
}
int countNum( void ){
int i , j , k , m , ans=-1;
for( j=0 ; j<sum ; j++ ){
if( ok( stack[j] , sit[1] ) ){ //如果j這種狀態和1的地勢狀態想匹配,即該情況可以布兵
dp[1][0][j] = num[j]; //記錄dp的初始條件
if( dp[1][0][j] > ans )
ans = dp[1][0][j];
}
}
for( i=2 ; i<=N ; i++ ) //從第二行開始
for( j=0 ; j<sum ; j++ ) //遍歷每一種布兵可行情況
for( k=0 ; k<sum ; k++ ){ //遍歷每一種布兵可行情況
if( ok(stack[j],stack[k]) && ok(stack[k],sit[i]) && ok(stack[j],sit[i-1]) ){//考慮i-1行和i行布兵情況均沒有衝突
for( m=0 ; m<sum ; m++ ){ //遍歷每一種布兵可行情況
if( ok(stack[k],stack[m]) && ok(stack[j],stack[m]) && ok(stack[m],sit[i-2]) ){//考慮i-1行和i-1行和i行布兵情況均沒有衝突(炮彈威力爲2)
dp[i][j][k] = max( dp[i][j][k] , dp[i-1][m][j]+num[k] );
if( dp[i][j][k]>ans )
ans = dp[i][j][k]; //選區最大值
}
}
}
}
return ans; //返回最多布兵數
}
int ok( int x , int y ){
if( x&y ) //當x和y對應的二進制中有對應位置同時出現1,即不滿足布兵要求
return 0;
return 1;
}
代碼分析:我是參考別人的貼寫的代碼,真是相當佩服大牛們,這是我第一次寫狀態壓縮的題,感覺這樣的算法便於理解,不過得注意位運算和加減法的優先級順序,適當加上括號防止不必要的錯誤;動態規劃中的狀態轉移方程爲dp[i][j][k]=max(dp[i-1][m][j]+num[k]),動歸效率比DFS高,節省了絕大部分時間.