[状态压缩DP] Poj 3254, Poj 1185

    状态压缩DP一般适合的题型的特征为:一个矩阵,行数较大,列数较小,每个点的状态只有两种。正好用一个整型数int来表示每行的一种状态(其实是其二进制形式,每bit的0和1表示每点的状态)。不同的是各题的状态dp的定义,状态间的限制,状态的转换方程。


第一道(Poj 3254):

/**
题意:在一片M行N列的草地上(用0和1矩阵表示),1表示能放牛,0表示不能放。
      在草地上放牛并且牛不能相邻,问有多少种放法(一头牛都不放也算一种)。

题解:对于每一行来说,放牛的可能数有2^N种,但是根据以下限制条件就能排除很多:
      1.每行中的牛不能相邻,经计算当N=12时,满足条件的状态只有377个
      2.每行中放牛要满足草地的硬件条件,只有1处可放,排除一些
      3.上一行中与本行对应的位置处不能放牛,排除一些
      
      由于N值最大为12,所以可以用一个二进制数来表示一行的状态,这就是“状态压缩”了。
      定义dp[i][j]:第i行的状态为state[j]时,前i行能放牛方法的总数.
*/

#include <cstdio>
#include <queue>
#include <algorithm>
#include <iostream>
#include <cstring>

using namespace std;

#define MOD 100000000

int n, m, row[12];
int nState, state[1000];
int dp[14][1000];

void init()
{
    int k = 1 << n;
    nState = 0;
    for (int i = 0; i < k; i++)
        if ( (i & (i<<1)) == 0 )
            state[nState++] = i;
}

int main()
{
    int k;

    scanf("%d %d", &m, &n);
    init();
    for (int i = 0; i < m; i++)
    {
        row[i] = 0;
        for (int j = n-1; j >= 0; j--)
        {
            scanf("%d", &k);
            row[i] += k << j;
        }
    }

    // 求dp[0],若state[j]满足第0行草地的硬件条件,则dp[0][j]=1
    for (int j = 0; j < nState; j++)
    {
        dp[0][j] = ( (row[0] & state[j]) == state[j] ) ? 1 : 0;
    }

    // 求dp[i],如果state[j]和第i-1行的状态state[k]不冲突,则dp[i][j]=Σ(dp[i-1][k])
    for (int i = 1; i < m; i++)
    {
        for (int j = 0; j < nState; j++)
        {
            if ( (row[i] & state[j]) != state[j] )  //判断是否满足草地硬件条件
                continue;
            for (int k = 0; k < nState; k++)
            {
                if ( dp[i-1][k] && (state[k]&state[j]) == 0 )
                    dp[i][j] = (dp[i][j] + dp[i-1][k]) % MOD;
            }
        }
    }

    int res = 0;
    for (int j = 0; j < nState; j++)
    {
        if ( dp[m-1][j] )
            res = (res + dp[m-1][j]) % MOD;
    }
    printf("%d\n", res);

    return 0;
}

第二道(Poj 1185):

/**
题意:在一个n行m列的矩阵中,字符'P'处能放炮兵,字符'H'处不能放炮兵。
      并且如果两个炮兵在一条(水平或垂直)直线上时,它们的距离不能小于2,问最多放多少个兵。

由于在求第i行时,它的状态要收到第i-1行和i-2行的影响,所以定义一个三维dp:
dp[i][j][k]表示第i行的状态为state[j],第i-1行的状态为state[k]时,前i行能放炮兵的最大数量。

for ( ... i < n ...)
{
    for ( ... j < nState ... )
    {
        如果状态j和第i行的地形不冲突,那么:
        for ( ... k < nState ... )
        {
            如果第i行状态j和第i-1行状态k不冲突,那么:
            for ( ... h < nState ... )
            {
                如果第i行状态j和第i-2行状态h不冲突,那么:
                在dp[i-1][k][h]中找最大的一个赋值给dp[i][j][k],再加上state[j]中1的个数
            }
        }
    }
}

*/

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int n, m, dp[105][80][80];      //dp[i][j][k]表示第i行的状态为j,第i-1行的状态为k时能放炮兵的最大数量

int nState, state[80], num[80]; //10位二进制位中各个1之间的距离不小于2,这样的数只有60个。
                                //依次存放在state[]中,num[i]表示state[i]中1的个数

//在小于2^m的数中找1之间的距离不小于2的数,保存在state[]中
void init()
{
    int k = 1 << m;
    nState = 0;
    for (int i = 0; i < k; i++)
        if ( (i&(i<<1)) == 0 && (i&(i<<2)) == 0 )
        {
            state[nState] = i;
            num[nState] = 0;
            int j = i;
            while (j)
            {
                num[nState] += j % 2;
                j /= 2;
            }
            nState++;
        }
}

int main()
{
    int  row[105];
    char str[15];

    while ( cin >> n >> m )
    {
        init();
        for (int i = 0; i < n; i++)
        {
            row[i] = 0;
            scanf("%s", str);
            for (int j = 0; j < m; j++)
                if (str[j] == 'P')
                    row[i] += 1 << j;
        }

        memset(dp, 0, sizeof(dp));

        // 计算dp[0]
        for (int j = 0; j < nState; j++)
        {
            if ( (state[j] & row[0]) != state[j] )
                continue;
            for (int k = 0; k < nState; k++)
                dp[0][j][k] = num[j];
        }

        // 计算dp[1]
        if (n > 1)
        for (int j = 0; j < nState; j++)
        {
            if ( (state[j] & row[1]) != state[j] )
                continue;
            for (int k = 0; k < nState; k++)
            {
                if ( (state[j] & state[k]) == 0 )
                    dp[1][j][k] = dp[0][k][0] + num[j];
            }
        }

        // 计算dp[>1]
        for (int i = 2; i < n; i++)
        {
            for (int j = 0; j < nState; j++)
            {
                if ( (state[j] & row[i]) != state[j] )
                    continue;
                for (int k = 0; k < nState; k++)
                {
                    if ( state[j] & state[k] )
                        continue;
                    for (int h = 0; h < nState; h++)
                    {
                        if ( state[j] & state[h] )
                            continue;
                        if ( dp[i-1][k][h] > dp[i][j][k] )
                            dp[i][j][k] = dp[i-1][k][h];
                    }
                    dp[i][j][k] += num[j];
                }
            }
        }

        // 在dp[n-1]中找最大值
        int max = 0;
        for (int j = 0; j < nState; j++)
        {
            for (int k = 0; k < nState; k++)
                if (max < dp[n-1][j][k])
                    max = dp[n-1][j][k];
        }

        printf("%d\n", max);
    }
}
---------------------- 更多“状态压缩DP”题目... ------------------------


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值