炮兵陣地-動態規劃/狀態壓縮/位運算

炮兵陣地
問題來源: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高,節省了絕大部分時間.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章