📜  与多米诺骨牌平铺(1)

📅  最后修改于: 2023-12-03 15:06:13.643000             🧑  作者: Mango

与多米诺骨牌平铺

多米诺骨牌平铺是一种经典的数学问题,在这个问题中,由28块1到6的有序骨牌组成,每个骨牌上的数字代表该骨牌两端出现的点数,任务是用这些骨牌将一个8x8的棋盘完全覆盖,不重复、不遗漏地铺满整个棋盘。

解决方法
回溯法

回溯法是解决这个问题的一种经典方法。该方法的基本思想是:从棋盘的左上角开始尝试铺放骨牌,每当尝试铺放一块骨牌的时候,实时判断当前情况是否可行,如果可行,则继续向下一块骨牌铺放,否则向上回溯。由于有28块骨牌,就会有28种选择。当遍历完全部的可能性后,如果没有发现合法的覆盖方法,就可以返回到上一层,重新选择新的铺放方式。

以下是回溯法的伪代码:

void solve(int x, int y) {
    if (x == n) {
        print(); // 输出结果
        return;
    }
    if (y == n) {
        solve(x+1, 0);
        return;
    }
    for (int i = 0; i < 28; i++) {
        if (dominoes[i].usage) continue;
        if (able_to_put(x, y, dominoes[i])) {
            put(x, y, dominoes[i]);
            solve(x, y+1);
            remove(x, y, dominoes[i]);
        }
    }
}

在该伪代码中,n代表棋盘的边长,dominoes是一个28个数值的数组,表示28种骨牌。该程序使用solve(x, y)来表示在(x, y)处尝试铺放骨牌。

精简版回溯法

在回溯法的基础上,可以使用一些技巧来简化算法。例如,可以将骨牌从左到右、从上到下依次编号,从而减少需要尝试的骨牌数量;在尝试放置骨牌的同时,可以判断该位置是否需要对称,从而减少无效状态的生成和判断。

以下是精简版回溯法的伪代码:

void solve(int x, int y) {
    if (x == n) {
        print(); // 输出结果
        return;
    }
    int r = y == 0 ? x : x+1, c = y == 0 ? y : y-1;
    if (c < n-1 && !board[r][c] && !board[r][c+1]) {
        for (int i = last+1; i < 28; i++) {
            if (dominoes[i].usage) continue;
            if (need_symmetric(r, c, dominoes[i])) continue;
            if (able_to_put(r, c, dominoes[i])) {
                put(r, c, dominoes[i]);
                dominoes[i].usage = true;
                last = i;
                solve(x, y+1);
                dominoes[i].usage = false;
                remove(r, c, dominoes[i]);
            }
        }
    } else {
        solve(x+1, 0);
    }
}

在该伪代码中,last是一个记录上一次尝试的骨牌编号的变量;board是一个数组,用于记录棋盘上每个位置是否被覆盖。同时,该程序使用need_symmetric(r, c, d)函数来判断当前尝试的骨牌是否需要对称。该函数可以使用一个hash表来预处理,也可以使用硬编码的判断方式来实现。

参考资料
  1. Problem 4708. Dominoes
  2. Domino tiling - Wikipedia