状态压缩DP
一、定义
通常将以一个集合内的元素信息作为状态且状态总数为指数级别的动态规划称为状态压缩动态规划;
二、原理
通过二进制位运算将状态压缩(用一个正数表示集合)作为动态规划的状态来解决问题;
三、分类
- 传统集合动态规划;
- 基于连通性状态压缩的动态规划;
四、特点
- 数据规模的一维或几维特别小;
- 需要具备动态规划问题的性质:最优性原理,无后效性原则;
五、使用条件
若集合大小不超过 nnn ,集合中每个元素都是小于 kkk 的自然数,则可以把这个集合看作是一个 nnn 位 kkk 进制数,以一个 [0,kn−1][0, k^n - 1][0,kn−1] 之间的十进制整数的形式作为 DPDPDP 状态的第一维;
六、例题
牧场的安排
题目
题目描述
Farmer John 新买了一块长方形的牧场,这块牧场被划分成 NNN 行 MMM 列 (1≤M≤12;1≤N≤12)(1 \leq M \leq 12; 1\leq N \leq 12)(1≤M≤12;1≤N≤12) ,每一格都是一块正方形的土地。FJ 打算在牧场上的某几格土地里种上美味的草,供他的奶牛们享用。遗憾的是,有些土地相当的贫瘠,不能用来放牧。并且,奶牛们喜欢独占一块草地,于是 FJ 不会选择两块相邻的土地,即:没有哪两块草地有公共边。当然,FJ 还没有决定在哪些土地上种草。
作为一个好奇的农场主,FJ 想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择。当然,把新的牧场荒废,不在任何土地上种草,也算一种方案。请你帮 FJ 算一下这个总方案数。
输入格式
第1 行:两个正整数 MMM 和 NNN ,用空格隔开;
第 2到 M+1M + 1M+1 行:每行包含 NNN 个用空格隔开的整数,描述了每块土地的状态。输入的第 i+1i + 1i+1 行描述了第 iii 行的土地。所有整数均为 0 或 1,1 表示这块土地足够肥沃,0 则表示这块地上不适合种草。
输出格式
第 1 行:输出一个整数,即牧场分配总方案数除以 10810^8108 的余数。
思路
由于选择草坪只有选与不选两种,并且 (1≤M≤12;1≤N≤12)(1 \leq M \leq 12; 1\leq N \leq 12)(1≤M≤12;1≤N≤12) ,所以考虑状态压缩 DPDPDP ;
状态
dp[i][j]dp[i][j]dp[i][j] :第 iii 行,状态为 jjj 时的状态方案总数;
转移
转移时,则枚举上一行的状态 kkk ,与当前行的状态 jjj ,先判断 jjj 与 kkk 是否合法:
-
判断竖着没有连着的草坪。这是我们可以把上一排和当前这一排进行与运算,如果值为 0 那么竖着没有连着的草坪;
-
判断左右没有连着的草坪。把当前这一行右移一位,再与原来的状态进行与运算,如果值为 0 即为合法;
-
判断有没有种到贫瘠的地方去。可以把当前行的贫瘠情况表示为二进制数,跟当前的种植情况的二进制数进行与运算,如果值为 0 那么则没有;
如果本行的 jjj 状态与上一行的 kkk 状态均合法,那么本行 jjj 状态的方案数就加上上一行 kkk 状态的方案数,说明 kkk 状态可以到 jjj 状态;
初始值则为第 0 行的所有状态均有一种方案;
最后答案为第 nnn 行所有状态总数之和即 ans=Σi=02m−1dp[n][i]ans = \Sigma_{i = 0}^{2^m - 1} dp[n][i]ans=Σi=02m−1dp[n][i];
状态转移方程
dp[i][j]+=dp[i−1][k](((j&(j<<1))==0)&&((j&tot[i])==j)&&((k&j)==0)&&((k&(k<<1))==0)&&((k&tot[i−1])==k))
dp[i][j] += dp[i - 1][k] (((j \& (j << 1)) == 0) \&\& ((j \& tot[i]) == j) \&\& ((k \& j) == 0) \&\& ((k \& (k << 1)) == 0) \&\& ((k \& tot[i - 1]) == k))
dp[i][j]+=dp[i−1][k](((j&(j<<1))==0)&&((j&tot[i])==j)&&((k&j)==0)&&((k&(k<<1))==0)&&((k&tot[i−1])==k))
代码
#include <cstdio>
#include <algorithm>
#define MAXN 6005
#define MOD 100000000
using namespace std;
int n, m, dp[25][MAXN], tot[25], ans = 0;
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
int x;
for (int j = 1; j <= m; j++) {
scanf("%d", &x);
tot[i] += x * (1 << (j - 1));
}
}
for (int i = 0; i <= (1 << m) - 1; i++) {
dp[0][i] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= (1 << m) - 1; j++) {
if (((j & (j << 1)) == 0) && ((j & tot[i]) == j)) {
for (int k = 0; k <= (1 << m) - 1; k++) {
if (((k & j) == 0) && ((k & (k << 1)) == 0) && ((k & tot[i - 1]) == k)) {
dp[i][j] += dp[i - 1][k];
dp[i][j] %= MOD;
}
}
}
}
}
for (int i = 0; i <= (1 << m) - 1; i++) {
ans += dp[n][i];
ans %= MOD;
}
printf("%d", ans);
return 0;
}