铺地砖、切蛋糕等等:
有一个 M * N的矩阵,现在需要使用 1 * 2 的骨牌进行覆盖,问总的覆盖方案数有多少?
注意点及思路:
通过数组记录每个1*1大小格子的覆盖状态,0为没有覆盖,1为已经覆盖;
那么在一个位置理论上存在,左边横放、右边横放;向上竖放例如,向下竖放4种;
但其实可以限制为 两种:如向右横放和向上竖放(因为上一行这一列位置的向下竖放跟这一行这一列位置的向上竖放是相同的);
那么就会出现例如上一行是【0,1,1,0】这种状态,下一行必须是【1,X,X,1】下一行的向上竖直放置才能填补上一行的空白(X表示可以放也可以不放)。
代码:
package class16;
public class Code02_PavingTile {
/*
* 2*M铺地的问题非常简单,这个是解决N*M铺地的问题
*/
// level表示,正在level行做决定
// N 表示一共有多少行 固定的
// level-2行及其之上所有行,都摆满砖了
// level做决定,让所有区域都满,方法数返回s
public static int ways2(int N, int M) {
if (N < 1 || M < 1 || ((N * M) & 1) != 0) {
return 0;
}
if (N == 1 || M == 1) {
return 1;
}
int big = N > M ? N : M;
int small = big == N ? M : N;
// big * small 长*宽
int sn = 1 << small;//1左移small位再减1可以使下行代码中的limit二进制为全1
int limit = sn - 1; // 全满状态
int[] dp = new int[sn];//dp用来记录铺法
dp[limit] = 1; // -1行全是满的 (初始状态)
// dp -1 行的状况
// dp[0000] 0
// dp[0001] 0
// dp[1111] 1
int[] cur = new int[sn]; // 当前行, 要算出所有状态下的解
//cur和dp两个数组通过数组复用的方式,记录了整个m*n的表的状态的更新
for (int level = 0; level < big; level++) {
for (int status = 0; status < sn; status++) {
if (dp[status] != 0) { // 状态出现了
// 0...1100
// 1...0011
// 0...1111
// 0...0011
//这边的操作主要是想从status,如000...1100的状态计算出如000...0011的op值
//status取反后为111...0011,limit为000...1111,两者相与就是000...0011即op的值 op的意义:例如0011意味着00位置可以横放一块
int op = (~status) & limit;
dfs(dp[status], op, 0, small - 1, cur);
}
}
for (int i = 0; i < sn; i++) {
dp[i] = 0;
}
int[] tmp = dp;
dp = cur;
cur = tmp;
}
return dp[limit];
}
public static void dfs(int way, int op, int index, int end, int[] cur) {
if (index == end) {//如果铺到最后一行结束了,就更新铺法
cur[op] += way;
} else {
dfs(way, op, index + 1, end, cur);
//又是一次牛逼的位运算
//例如:3:0000011
//op为110011100
//3左移index位后为001100000
//再与op相与,若为0则说明index位置有00,可以横放
if (((3 << index) & op) == 0) { // 11 << index 可以放砖
dfs(way, op | (3 << index), index + 1, end, cur);
}
}
}
public static void main(String[] args) {
int N = 8;
int M = 8;
System.out.println(ways2(N, M));
}
}