P2704 [NOI2001] 炮兵阵地(状压dp)
https://www.luogu.com.cn/problem/P2704
[NOI2001] 炮兵阵地
题目描述
司令部的将军们打算在 \(N\times M\) 的网格地图上部署他们的炮兵部队。
一个 \(N\times M\) 的地图由 \(N\) 行 \(M\) 列组成,地图的每一格可能是山地(用 \(\texttt{H}\) 表示),也可能是平原(用 \(\texttt{P}\) 表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示 \(N\) 和 \(M\)。
接下来的 \(N\) 行,每一行含有连续的 \(M\) 个字符,按顺序表示地图中每一行的数据。
输出格式
一行一个整数,表示最多能摆放的炮兵部队的数量。
样例 #1
样例输入 #1
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
样例输出 #1
6
提示
对于 \(100\%\) 的数据,\(1 \leq N\le 100\),\(1 \leq M\le 10\),保证字符仅包含 P
与 H
。
这个题和上面几个一样,就是状态多而已
首先,这是一道状压DP,因为数据量比较状压。。。
分别有\(N * M\)的地图,有缺陷的地形以及十字形的覆盖范围。
然后我们定义状态:
\(F[i][j][k]\)
用\(i\)表示当前枚举到第几行,\(j\)表示当前行的状态,\(k\)表示上一行的状态
#include<bits/stdc++.h>
using namespace std;
int n,m,F[105],f[105][66][66],start[70],cnt=0,gs[200];//特殊记录,不爆空间
bool mp[105][30];
int main(){
cin>>n>>m;
char a;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a;
if(a=='H')mp[i][j]=1;//另不能选的为1
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
F[i]=(F[i]<<1)+mp[i][j];
//把每行“不可选”状态压缩起来方便实用
}
}
start[++cnt]=0;
/*这个尤为重要,如果你要空间时间,就得像下面一样存
如果直接存的话,就没了(毕竟那个一整排H的数据还是有的)
不过如果从0开始循环也可以,但是放在这里起警示作用233*/
for(int i=1;i<(1<<m);i++){
if(i&(i<<1))continue; //因为左二右二不能选
if(i&(i<<2))continue;
if(i&(i>>1))continue;
if(i&(i>>2))continue;
start[++cnt]=i;//直接存有用的就行
int x=i;
while(x){ //求取每个状态的贡献
gs[cnt]++;
x-=(x&(-x));
}
}
for(int i=1;i<=cnt;i++){ //处理第一排
if((start[i]&F[1])==0){ //不能与地形冲突
f[1][i][0]=gs[i];
}
}
for(int i=1;i<=cnt;i++){ //第二排
if((start[i]&F[2])==0)
for(int j=1;j<=cnt;j++){
if((start[i]&start[j])==0&&(start[j]&F[1])==0){
//判断是否冲突
f[2][i][j]=gs[j]+gs[i];
}
}
}
//让for来的更猛烈些吧(枚举状态)
for(int i=3;i<=n;i++){
for(int j=1;j<=cnt;j++){ //当前一排状态
if((start[j]&F[i])==0){
for(int k1=1;k1<=cnt;k1++){ //上面第一排
if((start[j]&start[k1])==0&&(start[k1]&F[i-1])==0){
for(int k2=1;k2<=cnt;k2++){ //上面第二排
if((start[j]&start[k2])==0&&(start[k1]&start[k2])==0&&(start[k2]&F[i-2])==0){
//判断所有冲突情况
f[i][j][k1]=max(f[i][j][k1],f[i-1][k1][k2]+gs[j]);//从之前转移过来就行
}
}
}
}
}
}
}
int ans=0;
//所有的值都在最后1排存,用这一排的所有情况的最大值当最大值
for(int i=1;i<=cnt;i++){
for(int j=1;j<=cnt;j++){
ans=max(ans,f[n][i][j]);
}
}
cout<<ans;
}