状压DP。因为m<=10,所以把每一行部署炮兵部队的状态作为“状态”,二进制的每一位1表示部署,0表示没有。同时把山地表示为1,平原表示为0,第i行的地形为dx[i]。
因为从上到下考虑,考虑第i行时要考虑第i-1,i-2行,而考虑i-1行时又要考虑第i-2,i-3行……所以令f[i][j][k]表示前i行,第i行状态为j,第i-1行状态为k时可以部署炮兵部队的最大值。
可得以下方程:
$f[i][j][k]=max(f[i-1][k][l]+cnt[j])(j,k,lin S )$
同时j&k==0,j&l==0,j&dx[i]==0。
初始化:$f[0][0][0]=0$,其它为$-infty$。
S为一个行状态的合法集合,即状态中“1”两两之间间隔超过2。若一个状态i满足
!(i&(i<<1))&&!(i&(i<<2))
那么i是合法状态。
答案:$max(f[n][i][j]) (i,jin S)$
因为已初始化,所以找答案时可不用判断i&j是否为0。
事实上,m=10时|S|=60,为了节省空间,可以把j,k定义为集合中相应状态的编号(下标)。框架中只要改初始化:
$f[0][1][1]=0$(为什么,见代码↓)
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,s[102],cnt[102],tot;//s是可用集合,cnt是1的数目
int f[102][72][72];//f的2、3维都是可用状态的编号
//经试验m=10时|s|=60,所以可以放心少开
int dixing[102];//0平原,1山地,状态&地形!=0即炮兵在山地上
char str[13];
void input()//输入O(nm)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%s",str);
for(int j=0;j<m;++j)
{
dixing[i]<<=1;
if (str[j]=='H')
dixing[i]|=1;
}
}
}
int count1(int x)//计算1的个数
{
int sum=0;
while(x) x-=x&-x,++sum;
return sum;
}
void prework()//预处理合法集合O(2^m)
{
for(int i=0;i<1<<m;++i)
if (!(i&(i<<1))&&!(i&(i<<2)))
{
s[++tot]=i;
cnt[tot]=count1(i);//计算1的个数
}
}
int main()
{
input();
prework();
memset(f,0xcf,sizeof(f));//f数组初始化
f[0][1][1]=0;
for(int i=1;i<=n;++i)//DP O(n|s|^3)
for(int j=1;j<=tot;++j)
for(int k=1;k<=tot;++k)
for(int l=1;l<=tot;++l)
if ((s[j]&s[k])==0&&(s[j]&s[l])==0&&(s[j]&dixing[i])==0)
f[i][j][k]=max(f[i-1][k][l]+cnt[j],f[i][j][k]);
int ans=0;//寻找答案
for(int i=1;i<=tot;++i)
for(int j=1;j<=tot;++j)
ans=max(f[n][i][j],ans);
printf("%d",ans);
return 0;
}