【DP|狀態壓縮+預處理】POJ-1185 炮兵陣地

炮兵陣地
Time Limit: 2000MS Memory Limit: 65536K

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

Source
Noi 01


題意: 給出地圖,上面有些位置可以防止大炮,有些不能放,大炮可以攻擊其上下左右兩個格子的距離,要使大炮不互相攻擊,最多可以安放多少門大炮。
思路: 注意到地圖的大小很有意思,行有100,列卻只有10。這就是在說:狀態壓縮DP。
我是根據這個博客學會的:Titanium
那麼就要記錄我們需要知道的狀態,並且用二進制壓縮起來。
首先平原可以用0代替,高山可以用1代替。在某個位置放置大炮,影響它的是前兩行的該列有沒有別的大炮,有的話就不可以。暫時不考慮高山平原的話,我們可以把每一行的大炮放置情形用二進制全部儲存起來,然後暴力枚舉就行了。
每行狀態有多少呢?用代碼枚舉了一下,發現10列的行只有60整個狀態。
加上高山豈不是更少。
dp[r][i][j]表示第r行按照狀態i擺放而且其前一行狀態爲j的時候,一共擺放了多少大炮。
那麼它可以由它的前一行的狀態j以及前一行的前一行的狀態k直接推出:

dp[r][i][j] = max{dp[r][i][j], dp[r-1][j][k] + bomb[i]}

正如那篇博客作者所說:

DP要找到什麼因素影響了當前你要求的東西,有影響的我們就處理,沒影響的我們不用管

DP之前先判斷一下是否合法,也就是是否放在了高山上,用之前儲存的不互相攻擊的狀態按位與一下地圖的狀態即可。
代碼如下:

/*
 * ID: j.sure.1
 * PROG:
 * LANG: C++
 */
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <ctime>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <iostream>
#define PB push_back
#define LL long long
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
/****************************************/
const int N = 105, M = 15, S = 65;
char s[M];
int mat[N], state[S], bomb[S];
int n, m, tot;
int dp[N][S][S];
//狀態壓縮DP

void init()
{
    tot = 0;
    memset(mat, 0, sizeof(mat));
    memset(state, 0, sizeof(state));
    memset(bomb, 0, sizeof(bomb));
    memset(dp, 0, sizeof(dp));
}

void input()
{
    for(int i = 0; i < n; i++) {
        scanf("%s", s);
        for(int j = 0; j < m; j++) {
            if(s[m-j-1] == 'H') mat[i] |= 1 << j;
        }
    }
}

void count(int k)
{
    while(k) {
        bomb[tot] += k & 1;
        k >>= 1;
    }
}

void pre_treat()
{
    //不允許互相攻擊,存入state[]
    int lim = 1 << m;
    for(int i = 0; i < lim; i++) {
        if(i&(i>>1) || i&(i>>2)) continue;//左移右移都可以
        count(i);
        state[tot++] = i;
    }
}

void solve()
{
    //不允許放在山上
    for(int i = 0; i < tot; i++) {
        if(state[i] & mat[0]) continue ;
        dp[0][i][0] = bomb[i];//設-1行的狀態爲0
    }
    for(int i = 0; i < tot; i++) {//枚舉第二行
        if(state[i] & mat[1]) continue ;
        for(int j = 0; j < tot; j++) {//枚舉第一行
            if(state[j] & mat[0]) continue ;
            if(state[i] & state[j]) continue ;
            dp[1][i][j] = max(dp[0][j][0] + bomb[i], dp[1][i][j]);
        }
    }
    for(int r = 2; r < n; r++) {
        for(int i = 0; i < tot; i++) {
            if(state[i] & mat[r]) continue ;
            for(int j = 0; j < tot; j++) {
                if(state[j] & mat[r-1]) continue ;
                if(state[i] & state[j]) continue ;
                for(int k = 0; k < tot; k++) {
                    if(state[k] & mat[r-2]) continue ;
                    if(state[j] & state[k]) continue ;
                    if(state[i] & state[k]) continue ;
                    dp[r][i][j] = max(dp[r][i][j], dp[r-1][j][k] + bomb[i]);
                }
            }
        }
    }
}

int main()
{
#ifdef J_Sure
    freopen("000.in", "r", stdin);
    //freopen("999.out", "w", stdout);
#endif
    scanf("%d%d", &n, &m);
    init();
    input();
    pre_treat();
    solve();
    int ans = 0;
    for(int i = 0; i < tot; i++) {
        for(int j = 0; j < tot; j++) {
            ans = max(ans, dp[n-1][i][j]);
        }
    }
    printf("%d\n", ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章