1. 程式人生 > >POJ1185炮兵陣地(狀態壓縮DP)

POJ1185炮兵陣地(狀態壓縮DP)

沒有 set strong CI none 我們 ID 左右 names

POJ飛翔.數據弱

ZQOJ飛翔 數據強

Description

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

技術分享圖片

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

Input

多測試數據。

每組測試數據的第一行包含兩個由空格分隔的正整數,分別表示N和M; 0 ≤ N ≤ 100;0 ≤ M ≤ 10。

接下來的N行,每一行含有連續的M個字符(‘P‘或者‘H‘),中間沒有空格。按順序表示地圖中每一行的數據。

Output

每組測試數據輸出一行,包含一個整數K,表示最多能擺放的炮兵部隊的數量。

Sample Input

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

Sample Output

6

題目分析:

經典NOI題,矩陣裏的狀態壓縮問題。因為m<=10,而每列都有狀態選或不選,所以想到用2進制,那麽狀態數是2^10。因為當前行的選擇依賴於前兩行,而前一行又依賴於前前兩行,能想到狀態轉移方程應該牽扯到當前行、前一行、前前行,類似於遞推式dp[i] = dp[i-1] + dp[i-2]的遞推過程,而本體每次都是狀態間的轉移,想到狀態轉移方程dp[i][j][k] = max(dp[i][k][l]) + sum[j](j和k和l表示當前行狀態,前一行狀態,前前行狀態,sum[j]表示j狀態下在i行放了幾個大炮)。

用上面的轉移方程,空間復雜度和時間復雜度都不允許,因為j,k,l<=2^10,而實際的情況是10列的組合中不沖突的組合只有少數幾種,比如PHPP,狀態5(101)表示的在第0列和第2列放炮,這個狀態內部沖突,我們就可以不考慮,可以預處理把這些狀態剔除,然後將不沖突的狀態存進一個數組,轉移的時候用數組的下標去轉移就好。狀態轉移方程變成:dp[i][j][k] = max(dp[i][k][l]) + one[i][j](j,k,l分別表示第i行,第i-1行,第i-2行的第j個、第k個,第l個狀態,狀態分別為state[i][j],state[i-1][k],state[i-2][l],one[i][j]表示第i行狀態j的1的個數,也就是i狀態下放炮數量),最壞復雜度O(N*K^3)(K<62)

AC代碼:

技術分享圖片
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX = 110;
const int INF = 0x3f3f3f3f;
const int maxn = 70;
///state[i][j]表示第i行第j個合法狀態,one表示第i行第j個合法狀態中含1的個數
int state[MAX][MAX],one[MAX][MAX],ans;
///stnum[i]表示i行合法的狀態數,sum[i]為i狀態下1的個數
int stsum[MAX],sum[MAX*20];
///dp[i][j][k]表示第i行第j個狀態第-1行第k個狀態含有的最多1的個數
int dp[MAX][maxn][maxn],map[MAX][MAX],n,m;
void init( )
{
    ans=0;
    memset(dp,0,sizeof(dp));
    memset(map,0,sizeof(map));
    memset(one,0,sizeof(one));
    memset(stsum,0,sizeof(stsum));
}
//每個狀態裏面含有1的數量算出來
void onesum( )
{
    for(int i=0 ; i<=(1<<10) ; i++)
    {
        int t=0;
        for(int j=0 ; j<=10 ; ++j)
        if(i&(1<<j))
        t++;
        sum[i]=t;
    }
}
///判斷狀態是否符合
bool ok(int x)
{
    if(x>1&&(x&(x>>1)))
    return 0;
    if(x>2&&(x&(x>>2)))
    return 0;
    return 1;
}
///把第x行中合法的狀態全部找出來,存到state數組中,tot是本行所有的p點壓縮起來的一個狀態
void STATE(int x,int tot)
{
    for(int i=0 ; i<(1<<m) ; i++)
    if(ok(i)&&(i&tot)==i)
    ///(i&tot) == i表示集合i是集合tot的子集合,意思是i裏面的含有的列都是p點
    {
        stsum[x]++;
        int t=stsum[x];
        state[x][t]=i;
        one[x][t]=sum[i];
    }
}
int main( )
{
    onesum( );
    char tp[MAX];
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init( );
        stsum[0]=maxn;
        for(int i=1 ; i<=n ; i++)
        {
            scanf("%s",tp);
            int k=0;
            for(int j=0 ; j<m ; j++)
            {   //將圖變為0,1圖
                 map[i][j] = (tp[j] == P ? 1 : 0);
                k += (map[i][j] ? (1 << j) : 0);
            }
            STATE(i,k);

        }
        ///第一行
        for(int i=1 ; i<=stsum[1] ; i++)
        for(int j=1 ; j<=stsum[0] ; j++)
        dp[1][i][j]=one[1][i];
        for(int i=2 ; i<=n ; i++)
        for(int j=1 ; j<=stsum[i] ; j++)///枚舉第i行的狀態
        for(int k=1 ; k<=stsum[i-1] ; k++)///枚舉第i-1行的狀態
        {/// 判斷兩個狀態是否有沖突
            if(state[i][j]&state[i-1][k])
            continue;
            int t=0;
            for(int s=0 ; s<=stsum[i-2] ; s++)///枚舉第i-2行的狀態
            {
                if(state[i][j]&state[i-2][s])/// 判斷三個狀態是否有沖突
                continue;
                if(state[i-1][k]&state[i-2][s])
                continue;
                t=max(dp[i-1][k][s],t);
            }
            dp[i][j][k]=t+one[i][j];
        }
        for(int j=1 ; j<=stsum[n] ; j++)
        for(int k=1 ; k<=stsum[n-1] ; k++)
        ans=max(ans,dp[n][j][k]);
        printf("%d\n",ans);
    }
    return 0;
}
View Code

POJ1185炮兵陣地(狀態壓縮DP)