每一行的状态是取决于上一行和上上一行的,所以每次更新的时候需要记录当前行的状态和下一行的状态,然后再进行递推。。。不过估算了一下复杂度是10的11次方,吓得我都没敢写啊!!!看了一下别人的博客,居然真有这样写过的,于是就自己实现一边啦【最后看了以下讨论版,处理一下复杂度可以降低的,最后贴讨论版代码】
思路:令P为1,H为0。用一个数组存下每一行的状态。
1.先有数组num[] 存下各个状态的1的个数。
2.设dp[i][j][k] 为考虑了前i-1行,第i-1行的状态为j,第i-2行的状态为k的最大炮兵数。
那么有递推式dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][t] + num[j]);且j,k,t满足(j & k) = 0,(k & t) = 0,(j & t) = 0;
然后考虑特例n=1和边界n=2。
3.直接开数组应该会爆的,所以滚动数组实现。
我的代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 105;
const int maxm = 10;
int n,m,S[maxn];
int dp[2][1<<maxm][1<<maxm];
int num[1<<maxm];
void init(){
int tmp,cnt;
for(int i=0;i<(1<<maxm);i++){
cnt = 0;tmp = i;
while(tmp){
if(tmp & 1) cnt++;
tmp >>= 1;
}
num[i] = cnt;
}
}
bool islegal(int i,int x){
if((~S[i]) & x) return false;
if((x & (x << 1)) || (x & (x << 2)) || (x & (x >> 1)) || (x & (x >> 2))) return false;
return true;
}
int main(){
init();
scanf("%d%d",&n,&m);getchar();
for(int i=0;i<n;i++){
char ch;
for(int j=m-1;j>=0;j--){
ch = getchar();
if(ch == 'P') S[i] += (1 << j);
}
getchar();
}
int top = (1 << m);
if(n == 1){
int res = 0;
for(int j=0;j<top;j++) if(islegal(0,j)) res = max(res,num[j]);
printf("%d\n",res);
return 0;
}
for(int j=0;j<top;j++){
if(!islegal(1,j)) continue;
for(int k=0;k<top;k++){
if(islegal(0,k) && !(j & k))
dp[1][j][k] = num[j] + num[k];
}
}
for(int i=2;i<n;i++){
int v = i & 1,u = (i + 1) & 1;
for(int j=0;j<top;j++){
if(!islegal(i,j)) continue;
for(int k=0;k<top;k++){
if(!islegal(i-1,k) || (j & k)) continue;
for(int t=0;t<top;t++){
if((t & j) || (t & k)) continue;
dp[v][j][k] = max(dp[v][j][k],dp[u][k][t] + num[j]);
}
}
}
}
int u = (n - 1) & 1;
int res = 0;
for(int j=0;j<top;j++){
for(int k=0;k<top;k++){
res = max(res,dp[u][j][k]);
}
}
printf("%d\n",res);
return 0;
}
讨论版上某神牛的代码orz:
/*这题关键就是对状态的理解,由于每个炮能打2格的位置
我们可以先dfs出,每一行可能摆放的各种炮兵状态,canState[],canNum[]
经预先计算得出,m=10时最大的状态数为60
其次,没放过一行时,该行有两个有意义的状态。
一个是当前行的状态,一个是他上一行的状态。
这样一来,每一行就有60*60种状态。
我们可以定义dp[l][i][j],l表示第几层,i表示当前层的状态,j表示上一层的状态
那么 dp[l][i][j] = max{dp[l-1][j][k]} + canNum[i]
canNum[i]表示dfs预处理得出的第i个状态对应的编号
剩下的,就跟背包的更新极为类似了。
*/
#include<cstdio>
#include<cstring>
#define compatible(a,b) (((a)&(b))==0)
#define MN 110
#define MS 61
#define MM 11
/*dfs函数和它用到的全局变量
dfs函数,用于打表出每行m个位置时
各种可行的状态,数字为为1表示是炮*/
int canState[MS];
int canNum[MS];
int len;
int hillp;
void dfs(int loc,int num,int m);
/******************************/
/*input函数和它用到的全局变量
此函数功能为,输入数据,并处理
得到各层的hill,数位为1表示此位置不能放炮*/
char coteau[MN][MM];
int hill[MN];
void input(int n,int m);
/*****************************/
/*dpSolve函数和它所用到的全局变量
该函数功能是利用动态规划,计算出各层
各状态中,最多能放多少炮*/
int dpAmount[MN][MS][MS];
bool dpVisit[MN][MS];
void dpSolve(int n,int m);
/*************************/
int findmx(int n);//查找dpAmount中的最大值
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
input(n,m);
dpSolve(n,m);
printf("%d\n",findmx(n));
}
return 0;
}
void input(int n,int m)
{
int i,tmp;
char *p;
hill[0] = -2;
for(i=1;i<=n;i++)
{
scanf("%s",coteau[i]);
tmp = 0;
for(p=coteau[i];*p;p++)
{
tmp<<=1;
if(*p=='H')
tmp|=1;
}
hill[i]=tmp;
}
}
void dfs(int loc,int num,int m)
{
int i;
canNum[len] = num;
canState[len++]=hillp;
if(loc>=m-3)
{
return;
}
for(i=loc+3;i<m;i++)
{
hillp|=(1<<i);
dfs(i,num+1,m);
hillp&=(~(1<<i));
}
}
void dpSolve(int n,int m)
{
int i,j,k,l,mx;
//初始化dp所需的各元素
len=hillp=0;
dfs(-3,0,m);
memset(dpAmount,-1,sizeof(dpAmount));
memset(dpVisit,false,sizeof(dpVisit));
dpAmount[0][0][0] = 0;dpVisit[0][0]=true;
//进行dp
for(l=1;l<=n;l++)
{
for(i=0;i<len;i++)
{
if(compatible(canState[i],hill[l]))
{
for(j=0;j<len;j++)
{
if((!compatible(canState[i],canState[j]))||
(!dpVisit[l-1][j]))continue;
mx=-1;
for(k=0;k<len;k++)
{
if(dpAmount[l-1][j][k]>=0&&
compatible(canState[i],canState[k])&&
mx<dpAmount[l-1][j][k])
{
mx = dpAmount[l-1][j][k];
}
}
if(mx>=0)
{
dpAmount[l][i][j] = canNum[i] + mx;
dpVisit[l][i] = true;
}
}
}
}
}
}
int findmx(int n)
{
int mx=0;
int i,j,l;
for(l=1;l<=n;l++)
{
for(i=0;i<len;i++)
{
for(j=0;j<len;j++)
{
if(mx<dpAmount[l][i][j])
mx = dpAmount[l][i][j];
}
}
}
return mx;
}