poj3524 Corn Fields(状态压缩dp)

本文深入浅出地介绍了状态压缩动态规划的基本概念与实现方法,并通过一个具体的实例详细讲解了如何运用位运算和DP技巧解决复杂问题。文章还特别强调了状态压缩在不同问题中的应用差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前两天实验室没网,好好看了下状态压缩,现在编辑一下(2016/4/11)。

说真的,入门真的很难懂,难点有两个。位运算,dp关系的处理。


先说明什么是状态压缩:用一个数来表示一组数,以降低表示状态所需的维数的解题手段,就叫做状态压缩。 参考博客,真理get~


再声明各个数组的作用:

state[N],刚开始初始化状态,先对1左移宽度大小,然后从1开始记录每个状态。其中每个数字代表一个状态,存到state里。

cur[N],记录样例中每行的状态,如果是0,通过将1左移相应位置来代表0(注意1变0,0变1,这样方便求&),同样转化为十进制,存到cur里。


两个数组都弄好后,初始化dp第一行,通过对state[j]和cur[i]求&,找出2^n所有状态中对于当前状态可行(没有1是连续)的状态,将其初始化为1(一种方案)。

接下来的dp都是建立在第一行的基础上,数塔式递归。先遍历每行每列,然后由于要和前一状态比较(纵向判断),再遍历一次每列。为了判断是否可行加以剪枝,一种是该可能状态与当前行状态判断,不匹配即跳过。另一种是与上一行判断,不匹配即跳过。注意这里比较的是整行的状态,而不是单个状态。

最后把最后一行全加起来,即为总方案数(题中要求取模别忘了)。


然后是难点:

1、位运算。这里涉及到的位运算有两个,<<(左移)和&(与)。左移太简单不说,主要是&运算。

首先明白&运算的过程:4=0000 0000 0000 0100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100。说白了就是变为二进制后相同位置同时为1才能为1,否则为0。看似简单,但是可以做到我们好多想象不到的事。

(1)、本题中x & (x << 1)可以判断是否有1相邻,若相邻则求的一个不为0的数,返回false(因为相邻,所以该种情况不可行)。

(2)、本题中x & y可以判断是否冲突。

一种是state[j]&cur[i],因为本题cur中1即是0,所以一旦求的一个不为0的数,返回false(这个地方不能放玉米)。

一种是state[j] & state[k],即为上下行状态的比较,若相同位置同为1(都放玉米),求的不为0的数,跳过循环。

2、dp关系的处理。

并不是所有的状态压缩都可以像本题一样数塔式递归,不同的题用不同的dp方法,例如炮兵阵地,切记。


觉得经典所以写了解释,但感觉写了这么多也只是入了个门吧,泪。

#include <stdio.h>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 4500;
const int INF = 1e8;
const int mod = 100000000;

int n, m, top, total;
int state[N], cur[20], dp[20][N];

bool ok(int x)
{
    if(x & (x << 1)) return false;
    else return true;
}

void init()
{
    top = 0;
    total = 1 << n;
    for(int i = 0; i < total; i ++)
    {
        if(ok(i)) state[++ top] = i;
    }
}

bool fit(int x, int y)
{
    if(x & y) return false;
    else return true;
}

int main()
{
   // freopen("in.txt", "r", stdin);
    int num;
    while(~scanf("%d%d", &m, &n))
    {
        memset(dp, 0, sizeof(dp));
        init();
        //输入,同时记录该行的状态
        for(int i = 1; i <= m; i ++)
        {
            cur[i] = 0;
            for(int j = 1; j <= n; j ++)
            {
                scanf("%d", &num);
                if(num == 0) cur[i] += (1 << (n - j));
            }
        }
        //初始化第一行
        for(int i = 1; i <= top; i ++)
        {
            if(fit(state[i], cur[1])) dp[1][i] = 1;
        }
        //分层dp
        for(int i = 2; i <= m; i ++)
        {
            for(int j = 1; j <= top; j ++)
            {
                if(!fit(state[j], cur[i])) continue;
                for(int k = 1; k <= top; k ++)
                {
                    if(!fit(state[k], cur[i - 1])) continue;
                    if(state[j] & state[k]) continue;
                    dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;
                }
            }
        }
        //输出
        int ans = 0;
        for(int i = 1; i <= top; i ++)
            ans = (ans + dp[m][i]) % mod;
        printf("%d\n", ans);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值