状态压缩DP
这类题目初学有点难,所以本篇博客只有一道题目,另外还要有一道题目在下篇文章介绍。
f[i][j]
用i或者j表示某一种状态,其二进制的组合就是一种状态,例如5=(101)
就表示第一个位置选择,第二个位置不选择,第三个位置选择。题目
蒙德里安的梦想
题目描述
思路
状态的表示
当我们将横向排列的方式确定下来,那么竖着的排列也就只有一种情况,也即所有的方案数就等于横向排列的方案数,所有这里我们只考虑横向排列的方案即可。
这道题是很好的状态压缩的示例,对于这道题我们用f[i][j]
来表示前i-1列已经排列好,从i-1列伸到第i列的状态是j,的所有方案数。对于第i列状态j,假设j=4=(100)
,表示第一行和第二行没有从i-1列伸来,而第三行是从i-1列审过来的,也即i-1列的状态的二进制串的后三位一定是011。
状态转移方程
对于每一列i,我们枚举从0开始枚举状态j,根据我们的f[i][j]
的定义,那么如果第i列的某一位的状态是1,那么第i-1列的对应一位状态一定是0,否则就会冲突;其次我们考虑的只是横向排列的,我们求出的某一种方案一定都是横向的,那么我们就需要考虑那些没有填充的地方用竖着的方块是否可以放下。所以我们只需要考虑两种情况,枚举第i列的状态j的时候与第i-1列的状态k满足(j & k) == 0
且(j | k)
的二进制串不存在连续的奇数个0,那么f[i][j] += f[i -1][k]
如图所示:
代码实现:
import java.util.*;
public class Main{
public static void main(String[] args){
int N = 12,M = 1 << N;// N是行数,每一行都是一种状态所以M最大是12位二进制串所表示的最大值
long[][] f = new long[N][M];
boolean[] st = new boolean[M];
Scanner in = new Scanner(System.in);
while(true){
int n = in.nextInt();
int m = in.nextInt();
if(n == 0 && m == 0) break;
// st[i]指的是状态是i时,是否合法,即连续的0存在奇数不合法,否则合法
for(int i = 0;i < 1 << n;i ++){
int cnt = 0;
boolean p = true;
for(int j = 0;j < n;j ++){
// 遇到1就进行判断前边的连续的0是否是奇数个
if((i >> j & 1) == 1){
if(cnt % 2 == 1) p = false;
cnt = 0;
} else {
cnt ++;
}
}
// 最后一位也是0的时候进行的特判
if(cnt % 2 == 1) p = false;
st[i] = p;
}
for(int i = 0 ; i < N ; i ++ )
Arrays.fill(f[i],0); // 因为有多组数据,防止上一组数据的干扰,所以清零
f[0][0] = 1;// 从定义出发,不分割也是一种方案。
for(int i = 1;i <= m;i ++){
for(int j = 0;j < 1 << n;j ++){
for(int k = 0;k < 1 << n;k ++){
if((j & k) == 0 && st[j | k]) f[i][j] += f[i - 1][k];
}
if(i == m) break;
}
}
// 棋盘[0...m-1],所以第m列的状态一定是0
System.out.println(f[m][0]);
}
}
}