显然的我们把放置炮兵部队的位置用1表示,不放的位置用0表示,每一行用一个二进制数就可以表示放置情况。
这些放置要满足如下条件:(n表示行数 m表示列数)
- 每一行的炮台之间距离大于2 所有我们要预处理出所有满足1之间的距离大于2的m位的二进制数 即一个二进制数满足(i&(i>>1))!=0 && (i&(i>>2))!=0
- 表示当前行状态的二进制数必须和当前行的地形不冲突 即 (a&i)==i a是当前行的地形所得到的二进制数(H表示0 P表示1得到的二进制数) i 是当前行的放置状态
- 当前行的状态和前面两行的状态分别做并运算都是0 这一点应该好理解 因为不能冲突
- 由1,2点,我可以去掉很多无用的状态 设每 i 行可填的状态的集合为 事实证明 条件1下Si 的元素不超过70 并且结合条件2会变得更少(不超过20)
我们先把这些状态都记录下来 用数组的下标去标记这些状态 就可以快速的实现转移了 具体看代码
表示第 i 行 当前行状态为 j 上一行状态为 k 可以得到的方案数
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[102][70][70],n,m,val[70],b[102][70],num[70],tot;
char s[15];
void gethash(){
tot = 0;
for(int i = 0; i < (1<<m); i++)//记录满足条件1的状态
if((i&(i>>1))==0&&(i&(i>>2))==0)
val[++tot]=i;
for(int i = 1; i <= tot; i++){//记录二进制数1的个数
int g = val[i],ct = 0;
while(g){
g-=g&-g;
ct++;
}
num[i]=ct;
}
}
int main(){
// for(int i = 1; i <= tot; i++) printf("val[%d]=%d\n",i,val[i]);
while(~scanf("%d%d",&n,&m)){
gethash();
//for(int i = 1; i <= tot; i++) printf("val[%d]=%d num[%d]=%d\n",i,val[i],i,num[i]);
for(int i = 1; i <= n; i++){
scanf("%s",s);
int has = 0;
for(int j = 0; j < m; j++)//计算地形所表示的状态
has=has*2+(s[j]=='P');
b[i][0]=1;
for(int j = 1; j <= tot; j++)//根据地形(条件2)和条件1 把状态集合再缩小
if((has&val[j])==val[j]) b[i][b[i][0]++]=j;
}
b[0][0]=2;b[0][1]=0;
memset(dp,0,sizeof(dp));
int ans=0;
for(int i = 1; i < b[1][0]; i++) dp[1][i][1]=num[b[1][i]],ans=max(dp[1][i][1],ans);//初始化
for(int i = 2; i <= n; i++){
for(int j = 1; j < b[i][0]; j++){
for(int k = 1; k < b[i-1][0]; k++){
if((val[b[i][j]]&val[b[i-1][k]])!=0) continue;//条件3
for(int g = 1; g < b[i-2][0]; g++){
if((val[b[i-1][k]]&val[b[i-2][g]])!=0) continue;//条件3
if((val[b[i][j]]&val[b[i-2][g]])!=0) continue;//条件3
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][g]+num[b[i][j]]);
ans=max(ans,dp[i][j][k]);
}
}
}
}
printf("%d\n",ans);
//for(int i = 1; i <= n; i++) printf("i=%d a[i]=%d\n",i,a[i]);
}
return 0;
}