题解 | #炮兵阵地#
炮兵阵地
https://ac.nowcoder.com/acm/problem/16886
思路:
题目要求求出最多的炮兵数,对于一个位置可以选择放也可以不放,而之后可以根据这次的选择更新下一步的选择。考虑状态压缩dp。一行一行的放炮兵,因为题目中给出的炮兵攻击范围是一个十字,所以在判断这个炮兵能不能放的时候需要知道上一行炮兵和上上行放在哪了,也要知道此时放的炮兵所在的这一行里左边的放炮兵的情况,可以用一个01串来表示每一行放炮兵的情况,用位运算&来比较这一行与上一行或者上上行的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=110;
int dp[N][70][70];//dp[i][j][k]表示放了i行,现在状态为j,之前状态为k的最大炮兵数
int n,m;
int M[N];
vector<int> num;
vector<int> st;
int cal(int i)
{
int cnt=0;
while(i)
{
if(i&1) cnt++;
i>>=1;
}
return cnt;
}
int main(){
cin>>n>>m;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
char c;
cin>>c;
if(c=='H') M[i] |= (1<<(m-j));
/*把每一行的地形都当作01串,如果是山地,就初始化为1,否则为0.在
后面的操作中,放了炮兵的格子为1,如果(此时状态)&(这一行地形的01串值)
=1的话,就说明在山地上放了炮兵,那么这种情况就不行。如果值为0,
有3种情况:1、平原上放了炮兵 2、平原上没放炮兵3、山地上没放炮兵。
1<<(j-1)是反着存地图里的山地的,答案和正着存没区别。
*/
}
}
for(int i=0; i<(1<<m); i++)//先把一整行可行的情况存下来,到用的时候就不用再算一遍了,每种状态都是01串,1为放炮兵的点,0是不放炮兵
{
if(i & (i<<1)) continue;//如果有两个相邻的1,跳过
if(i & (i<<2)) continue;//如果有两个1只相差1位,跳过
num.push_back(cal(i));//num存这一种情况下有几个炮兵
st.push_back(i);//st存这一种情况的01串
}
int ans=0;
for(int i=1; i<=n; i++)
{
for(int j=0; j<st.size(); j++)
{
int cur=st[j];
if(cur & M[i]) continue;//如果在山地上放炮兵,跳过
for(int k=0; k<st.size(); k++)
{
int last=st[k];
if(cur & last) continue;//如果现在状态和上一次状态有某个炮兵在同一列,跳过(注意:上一行炮兵在这一行炮兵的斜上方是没问题的,他们只是攻击范围重叠了)
for(int u=0; u<st.size(); u++)
{
int tmp=st[u];
if(tmp & last) continue;//如果上一次状态和上上次状态有某个炮兵在同一列,跳过
if(tmp & cur) continue;//如果现在状态和上上次状态有某个炮兵在同一列,跳过
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][u]+num[j]);//直接用i、j、k,就是用状态的编号来表示,节约空间
ans=max(dp[i][j][k],ans);
}
}
}
}
cout<<ans<<endl;
return 0;
}