状态压缩DP POJ 1185 炮兵阵地

【题目链接】http://poj.org/problem?id=1185

【题目大意】在平原(P)上可以布置一支炮兵部队,而在山地(H)上不能够部署炮兵部队。而且每两个炮兵之间的间隔(横纵)要大于等于 2,求最多能够摆放多少的炮兵部队。

【解题思路】经典状态压缩DP。每行炮兵的状态 i,只与i-1,和i-2行状态有关。若用0表示此处不能放,1表示能放。则根据题意可知:1、有些地方不能放置;2、每种状态中任意两个1之间的间隔要大于等于2(保证横着不互相攻击)。因为是和前两行的状态有关,所以要开个三维的数组来表示状态,当前行的状态可由前两行的状态转移而来。即如果当前行的状态符合前两行的约束条件(不和前两行的大炮互相攻击),则当前行的最大值就是上一个状态的值加上当前状态中1的个数(当前行放大炮的个数) 

【状态表示】dp[i][j][k] 表示第i行的状态为第k个状态,第i-1状态为第j 个状态时的最大炮兵个数。 

【状态转移方程】 dp[i][j][k] = max(dp[i][j][k], dp[i-1][z][j] + num[k]);num[k]为第k个状态中1的个数 

【DP边界条件】dp[1][1][i] =num[i] 状态i能够满足第一行的硬件条件(注意:这里的i指的是第i个状态,不是一个二进制数,开一个数组保存二进制状态) 

【代码实现】

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;

char str[110][20];
int h_pos[110], p_pos[110];
int n, m;
int num[250];
int top;
int dp[110][60][60];


bool ok(int x)
{
    if(x & (x << 1)) return 0;
    if(x & (x << 2)) return 0;
    return 1;
}

void init()
{
    int tot = 1 << m;
    top = 0;
    for(int i = 0; i < tot; i ++)
    {
        if(ok(i)) p_pos[++top] = i;
    }
}
//计算一个数的二进制数中1的个数,即找每种状态中能放置炮兵的个数
int count1(int x)
{
    int cnt = 0;
    while(x)
    {
        //if(x & 1) cnt ++;
       // x >>= 1;
       cnt ++;
       x &= (x-1);
    }
    return cnt;
}

//判断状态x是否适合第y行,即判断第y行中‘H’的位置与炮兵的位置是否冲突
bool isfit(int x, int y)
{
    if(h_pos[y] & x) return 0;
    return 1;
}
int  main()
{
    //输入n行m列个字符,每行仅由P和H组成
    while(~scanf("%d %d", &n, &m) && n+m)
    {
        for(int i = 1; i <= n; i ++)
        {
            scanf("%s", str[i]+1);
        }

        //两个炮兵之间的间距不能小于3,筛选符合条件的
        init();

        //计算每行中H的个数,并用二进制的形式进行存储在数组h_pos中
        for(int i = 1; i <= n; i ++)
        {
            h_pos[i] = 0;
            for(int j = 1; j <= m; j ++)
            {
                if(str[i][j] == 'H')
                    h_pos[i] += (1 << (j-1));
            }
        }
       // printf("%d\n", top);
        memset(dp, -1, sizeof(dp));
        for(int i = 1; i <= top; i ++)
        {
            num[i] = count1(p_pos[i]);
           // printf("num[i]=%d \n", num[i]);
            if(isfit(p_pos[i], 1))
                dp[1][1][i] = num[i];
        }

        for(int i = 2; i <= n; i ++)
        {
            for(int k = 1; k <= top; k ++)
            {
                if(!isfit(p_pos[k], i)) continue;
                for(int j = 1; j <= top; j ++)
                {

                    if(p_pos[j] & p_pos[k]) continue;
                    for(int z = 1; z <= top; z ++)
                    {
                        if(p_pos[z] & p_pos[k]) continue;
                        if(dp[i-1][z][j]== -1) continue;
                        dp[i][j][k] = max(dp[i][j][k], dp[i-1][z][j] + num[k]);
                    }
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= top; j ++)
                for(int k = 1; k <= top; k ++)
                    ans = max(ans, dp[i][j][k]);
        printf("%d\n", ans);
    }

    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值