题意:
给你一个
n
∗
m
n∗m
n∗m的网格,每个各自最多放一个炮兵,每一个格子有地形,平原可以放,山地不能放,并且一个炮兵的上下左右两个内不能放炮兵,问最多放多少炮兵。
n
≤
100
;
m
≤
10
n≤100;m≤10
n≤100;m≤10。
可以发现m非常小,考虑状压dp,这道题与常规状压dp的不同点是这道题的每一行状态在转移时要看之前两行的状态。
首先我们先将每一行哪些格子可以放用二进制表示出来, g [ i ] g[i] g[i]表示第 i i i行地形情况的二进制状态。然后处理出一行内所有合法的情况。我们设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示第 i i i行,当前状态的 j j j,上一行的状态是 k k k最多放多少炮兵,我们按顺序枚举当前行,上一行,和上两行的状态,要保证当前行的状态不与当前行的地形状态冲突,当前行的状态不与上一行和上两行的状态冲突,上一行和上两行的状态不冲突。
所以 d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ k ] [ q ] + s [ j ] ) dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][q]+s[j]) dp[i][j][k]=max(dp[i][j][k],dp[i−1][k][q]+s[j]), s [ j ] s[j] s[j]为 j j j状态下 1 1 1的个数,即为该状态下可以放的炮兵的个数。最后答案就是 m a x max max{ d p [ n ] [ i ] [ j ] dp[n][i][j] dp[n][i][j]}, i , j i,j i,j为最后一行和倒数第二行的所有状态。
#include<bits/stdc++.h>
using namespace std;
int n,m,g[1000000],num,res[1000000],ans,dp[105][1024][1024],s[1000000];
char st[1001000];
//g数组为第i行山地平原情况的状压,0为平原,1为山地
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%s",st+1);
for(int j=1;j<=m;++j)
{
if(st[j]=='H')
g[i]+=1<<(j-1);
}
}
for(int i=0;i<(1<<m);++i)
{
if(!(i&(i>>1))&&!(i&(i>>2))&&!(i&(i<<1))&&!(i&(i<<2)))
{
res[++num]=i;//可行状态
int w=i;
while(w)
{
if(w%2==1)
s[num]++;//该状态下1的个数(放的炮的个数)
w/=2;
}
if(!(i&(g[1])))//初始化第一行状态
dp[1][num][0]=s[num];
}
}
for(int i=1;i<=num;++i)//枚举第一行的状态
for(int j=1;j<=num;++j)//枚举第二行的状态
if(!(res[i]&res[j])&&!(g[2]&res[j]))//判断是否与地形和第一行冲突
dp[2][j][i]=max(dp[2][j][i],dp[1][i][0]+s[j]);
for(int i=3;i<=n;++i)
for(int j=1;j<=num;++j)//当前行的状态
if(!(g[i]&res[j]))//当前行的状态不与地形冲突
for(int k=1;k<=num;++k)//前一行的状态
if(!(res[k]&res[j]))//前一行的状态与当前行不冲突
for(int q=1;q<=num;++q)//前两行的状态
if(!(res[q]&res[k])&&!(res[q]&res[j]))//前两行的状态不与前一行的状态和当前行的状态冲突
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][q]+s[j]);
for(int i=1;i<=num;++i)
for(int j=1;j<=num;++j)
ans=max(ans,dp[n][i][j]);
cout<<ans;
return 0;
}