Mondriaan s Dream POJ2411图文详解(状态压缩DP)

在以前我们学习的“线性DP”中,我们提到,动态规划的过程是随着“阶段”的增长,在每个状态维度上不断扩展的。在任意时刻,已经求出最优解的状态与尚未求出最优解的状态在各维度上的分界点组成了DP扩展的“轮廓”。对于某些问题,我们需要在动态规划的“状态”中记录一个集合,保存这个“轮廓”的详细信息,以便进行状态转移。若集合大小不超过N,集合中每个元素都是小于K的自然数,则我们可以把这个集合看作一个N位K进制数,以一个[0,KN-1]之间的的十进制整数的形式作为DP状态的一维。这种把集合转化为整数记录在DP状态中的一类算法,就被称为状态压缩的动态规划算法。

题目大意:给定一个 NM 的棋盘,用 12 的木条填满有多少种不同的方式。
例如当N=2,M=4时,共有5中方案。当N=2,M=3时,有3中方案。如下图所示:
在这里插入图片描述
Sample Input
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

Sample Output
1
0
1
2
3
5
144
51205
在这里插入图片描述
对于任意一种方案,考虑以某一行为界,把整个棋盘横着分成两半,如上图所示。在上半部分最后一行中:
1.每个灰色背景的部分都是一个竖着的12长方形的一半,决定了下一行必须继续补全该长方形。
2.其余部分对下半部分的分割方法没有影响。下一行既可以在连续两个位置安排一个横着的1
2长方形,也可以让某个位置作为一个竖着的1*2长方形的一半。

综上所述,我们可以把“行号”作为DP的“阶段”,把“上半部分”不断向下扩展,直至确定整个棋盘的分割方法。为了描述上半部分最后一行的详细形态,我们可以使用一个M位二进制数,其中第k(0≤k<M)位为1表示第k列是一个竖着的1*2长方形的上面一半,第k位为0表示其他情况。

设F[i,j]表示第i行的形态为j时,前i行分割方案的总数。j是用十进制整数记录的M位二进制数。
第i-1行的形态k能转移到第i行的形态j,当且仅当:

  1. j和k执行按位与运算的结果是0。
    这保证了每个数字1的下方必须是数字0,代表继续补全竖着的1*2长方形。
  2. j和k执行按位或运算的结果的二进制表示中,每一段连续的0都必须有偶数个。
    这些0代表若干个横着的1*2长方形,奇数个0无法分割成这种形态。

我们可以在DP前预处理出[0,2M-1]内所有满足“二进制表示下每一段连续的0都有偶数个”的整数,记录在集合S中。

在这里插入图片描述
完整代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
int n, m;
long long f[12][1 << 11];
bool in_s[1 << 11];

int main()  {
 while (cin >> n >> m && n) {
  for (int i = 0; i < 1 << m; i++) {
   bool cnt = 0, has_odd = 0;
   for (int j = 0; j < m; j++)
    if (i >> j & 1) has_odd |= cnt, cnt = 0;
    else cnt ^= 1;
   in_s[i] = has_odd | cnt ? 0 : 1;
   /*
   1、每次右移,用1和最后一位按位与判断是1还是0,对于连续个0,cnt的值1和0交替变化,奇数个0,cnt的值是1,偶数个0,cnt的值是0。
   2、只要来个1,执行has_odd|=cnt,就把上次连续个0的结果保留到has_odd中,只要has_odd=1就不会再改变了(因为has_odd和cnt执行的按位或操作),说明曾经出现过连续奇数个0。同时把cnt复位为0,重新计算下一组连续个0的情况。
   */
  }
  f[0][0] = 1;
  for (int i = 1; i <= n; i++)
   for (int j = 0; j < 1 << m; j++) {
    f[i][j] = 0;
    for (int k = 0; k < 1 << m; k++)
     if ((j&k) == 0 && in_s[j | k])
      f[i][j] += f[i - 1][k];
   }
  cout << f[n][0] << endl;
 }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值