题目链接:POJ 炮兵阵地
Description
司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由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
思路:
题目是求最优解的问题,首先看数据,最大是100 * 10的地图,若是搜索的话对于每个‘P’都有放和不放两种情况,肯定爆,因此考虑dp。
描述dp状态
一行一行考虑放置情况进行递推,所以行数作为一个参数。但“前 i 行能摆放的方法数”这个状态值并不能由其他状态推出,所以还需要加维度,发现一门炮的射程是2,那当前位置能否摆一门炮,取决于前两行这个位置的放置情况,因此最新两行的摆放情况也要作为描述状态的参数,刚好地图的宽度<=10,可以把一行的摆放状态状压成一个int,1代表放置,0代表不放。dp[i][j][k]表示第i行的状态是j,上一行状态是k的摆法最大种数。
但是,状压后表示一行摆放情况的整数范围大概是[0, 2^10],开设dp[100][1024][1024]的数组太大,所以要预处理,因为同一行并不可能摆满炮,它们至少间隔为2,这样一行的摆法会少很多。
dfs预处理
void dfs(int x, int pos){// x表示一行中的第几个位置,pos是状压的摆放情况
if( x > M-1){ // M是地图列数
states[depth++] = pos;
return ;
}
dfs( x + 1, pos); // 当前位置不放
pos |= ( 1 << x);
dfs( x + 3, pos) ; // 当前位置放
}
调用dfs(0, 0)可以预处理出所有摆法存起来,states[i]表示第i种摆法。
假设地图宽10,且全部是平地,算出来最多有60种摆法,所以开设dp[110][70][70]即可。
递推之前,先对前两行初始化:(num(j)表示第j种摆法的炮数)
dp[0][j][0] = num(j)
dp[1][i][j] = max{dp[0][j][0]} + num(i)
状态转移方程:
dp[i][j][k] = max{dp[i-1][k][m], m = 0…depth} + num(j),
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int N, M;
int states[70];
int depth = 0;
int dp[110][70][70];
char area[110][20];
void dfs(int x, int pos){// x表示一行中的第几个位置,pos是状压的摆放情况
if( x > M-1){ // M是地图列数
states[depth++] = pos;
return ;
}
dfs( x + 1, pos); // 当前位置不放
pos |= ( 1 << x);
dfs( x + 3, pos) ; // 当前位置放
}
int main(){
cin >> N >> M;
dfs(0, 0);
for( int i = 0; i < N; ++i)
for( int j = 0; j < M; ++j)
cin >> area[i][j];
for( int i = 0; i < depth; ++i){// 初始化第一层
int cnt = 0;
for( int j = 0; j < M; ++j){
if( (states[i] & (1 << j)) != 0 && area[0][j] == 'H'){
cnt = 0;
break;
}
if( (states[i] & (1 << j)) != 0)
cnt++;
}
dp[0][i][0] = cnt;
}
for( int i = 0; i < depth; ++i){// 初始化第二层
int nowmax = 0;
for( int k = 0; k < depth; ++k){
if( (states[i] & states[k]) != 0){// ik不相容
dp[1][i][k] = 0;
continue;
}
bool illegal = false;
for( int j = 0; j < M; ++j){// ik相容则判断地形
if( (states[i] & (1 << j)) != 0 && area[1][j] == 'H'){// 不合法,与地形冲突
dp[1][i][k] = 0;
illegal = true;
break;
}
}
if( illegal == false){// 表明ik相容且与地形也不冲突
int cnt = 0;
for( int j = 0; j < M; ++j)
if( (states[i] & (1 << j)) != 0)
cnt++;
dp[1][i][k] = dp[0][k][0] + cnt;
}
}
}
for( int i = 2; i < N; ++i){ // 开始递推每一层 dp[i][j][k]
for( int j = 0; j < depth; ++j){
bool illegal = false;
for( int p = 0; p < M; ++p){// 判断地形
if( (states[j] & (1 << p)) != 0 && area[i][p] == 'H'){ //不合法,与地形冲突
illegal = true;
break;
}
}
if( illegal == true)
continue;
//若地形合法,看jk
for( int k = 0; k < depth; ++k){
if( (states[j] & states[k]) != 0){// jk不相容
dp[i][j][k] = 0;
continue;
}
int nowmax = 0;
for( int m = 0; m < depth; ++m){
if( (states[j] & states[m]) == 0)// 第i行必须要和第i-2行不冲突
nowmax = max( nowmax, dp[i-1][k][m]);
}
int cnt = 0;
for( int p = 0; p < M; ++p)
if( (states[j] & (1 << p)) != 0)
cnt++;
dp[i][j][k] = nowmax + cnt;
}
}
}
int themax = 0;
for( int i = 0; i < N; ++i){
for( int j = 0; j < depth; ++j){
for( int k = 0; k < depth; ++k)
themax = max( themax, dp[i][j][k]);
}
}
cout << themax << endl;
return 0;
}