C - 炮兵阵地 (状态dp)

题意:
类似于上面一道题,一个方格组成的矩阵,每个方格可以放大炮用0表示,不可以放大炮用1表示(原题用字母),让放最多的大炮,大炮与大炮间不会互相攻击。
解析:
可以发现,对于每一行放大炮的状态,只与它上面一行和上上一行的状态有关,每一行用状态压缩的表示方法,0表示不放大炮,1表示放大炮,同样的,先要满足硬件条件,即有的地方不能放大炮,然后就是每一行中不能有两个1的距离小于2(保证横着不互相攻击),这些要预先处理一下。然后就是状态表示和转移的问题了,因为是和前两行的状态有关,所以要开个三维的数组来表示状态,当前行的状态可由前两行的状态转移而来。即如果当前行的状态符合前两行的约束条件(不和前两行的大炮互相攻击),则当前行的最大值就是上一个状态的值加上当前状态中1的个数(当前行放大炮的个数) 
【状态表示】dp[i][j][k] 表示第i行状态为k,第i-1状态为j时的最大炮兵个数。 
【状态转移方程】dp[i][k][t] =max(dp[i][k][t],dp[i-1][j][k]+num[t]); num[t]为t状态中1的个数 
【DP边界条件】dp[1][1][i] =num[i] 状态i能够满足第一行的硬件条件(注意:这里的i指的是第i个状态,不是一个二进制数,开一个数组保存二进制状态) 
代码:
#include <cstdio>
#include <cstring>
using namespace std;
#define max(a,b) (a) > (b) ? (a) : (b)
 
int N,M;
char map[110][20],num[110],top;
int stk[70],cur[110];
int dp[110][70][70];
 
inline bool ok(int x){  //判断该状态是否合法,即不存在相邻的1之间的距离小于3的
   if(x&(x<<1)) return 0;
   if(x&(x<<2)) return 0;
   return 1;
}
//找到所有可能的合法状态,最多60种
inline void jinit()
{
   top=0;
   int i,total=1<<N;
   for(i=0;i<total;i++) if(ok(i)) stk[++top]=i;
}
//判断状态x是否与第k行匹配
inline bool fit(int x,int k)
{
   if(cur[k]&x) return 0;
   return 1;
}
//数一个整型数x的二进制中1的个数(用于初始化)
inline int jcount(int x)
{
   int cnt=0;
   while(x)
   {
       cnt++;
       x&=(x-1);
   }
   return cnt;
}
 
int main(){
   while(scanf("%d%d",&M,&N) != EOF){
       if(N == 0 && M == 0)break;
       jinit();
       for(int i = 1; i <= M; ++i)scanf("%s",map[i]+1);
       for(int i = 1; i <= M; ++i)
       for(int j = 1; j <= N; ++j){
           cur[i]=0;
           for(j=1;j<=N;j++){
                if(map[i][j]=='H')cur[i]+=(1<<(j-1));
           }
       }
       memset(dp,-1,sizeof(dp));
 
       //初始化第一行状态
       for(int i = 1;i <= top;i++){
           num[i]=jcount(stk[i]);
           if(fit(stk[i],1))
                dp[1][1][i]=num[i];
       }
       int i,t,j,k;
       for(i = 2;i <= M;i++){
           for(t = 1;t <= top;t++){
                if(!fit(stk[t],i)) continue;
                for(j = 1;j <= top;j++)
                {
                    if(stk[t]&stk[j])continue;
                    for(k = 1;k <= top;k++)
                    {
                        if(stk[t]&stk[k])continue;
                        if(dp[i-1][j][k]==-1)continue;
                        dp[i][k][t] =max(dp[i][k][t],dp[i-1][j][k]+num[t]);
                    }
                }
           }
       }
       int ans = 0;
       for(i = 1; i <= M; ++i)
       for(j = 1; j <= top; ++j)
       for(k = 1; k <= top; ++k)
       ans = max(ans,dp[i][j][k]);
       printf("%d\n",ans);
   }
   return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值