描述
一张普通的国际象棋棋盘,它被分成 8 乘 8 (8 行 8 列) 的 64 个方格。设有形状一样的多米诺牌,每张牌恰好覆盖棋盘上相邻的两个方格,即一张多米诺牌是一张 1 行 2 列或者 2 行 1 列的牌。那么,是否能够把 32 张多米诺牌摆放到棋盘上,使得任何两张多米诺牌均不重叠,每张多米诺牌覆盖两个方格,并且棋盘上所有的方格都被覆盖住?我们把这样一种排列称为棋盘被多米诺牌完美覆盖。这是一个简单的排列问题,同学们能够很快构造出许多不同的完美覆盖。但是,计算不同的完美覆盖的总数就不是一件容易的事情了。不过,同学们 发挥自己的聪明才智,还是有可能做到的。
现在我们通过计算机编程对 3 乘 n 棋盘的不同的完美覆盖的总数进行计算。
输入
一次输入可能包含多行,每一行分别给出不同的 n 值 ( 即 3 乘 n 棋盘的列数 )。当输入 -1 的时候结束。
n 的值最大不超过 30.
输出
针对每一行的 n 值,输出 3 乘 n 棋盘的不同的完美覆盖的总数。
样例输入
2
8
12
-1
样例输出
3
153
2131
分析
这道题需要我们找出n取不同值时相互之间的关系。
假设f(n)为列数为n时可完美覆盖方案的总数。
首先n为奇数时格子数就会不够,是不可能填充成功的。
我们把 3 x n 的棋盘分为左、右两部分,右边为不可分割的棋盘,像下图这样:
比如按上图所示分割,右边为2列时有三种情况,所以f(n)=3*f(n-2)
分割线向左递进,下个方案右侧可以分割成4列,右边区域不可分割的情况只有下面这种铺设方案x2(上、下翻转):
右边为6列时:
以此类推可知递推公式:
f(n)=3f(n-2)+2f(n-4)+2f(n-6)+...+2f(0)
用f(n)-f(n-2)简化公式得到:
f(n)=4f(n-2)-f(n-4)
实现
#include <iostream>
#include <cstring>
using namespace std;
int f[31];
int main()
{
// freopen("in.txt", "r", stdin);
memset(f, 0, sizeof(f));
f[0] = 1;
f[2] = 3;
for (int i = 4; i <= 30; i += 2) {
f[i] = 4 * f[i - 2] - f[i - 4];
}
int n;
while(cin >> n && n != -1) {
cout << f[n] << endl;
}
return 0;
}
此题的解法就是这样,这是行数为3的情况,如果未定的话可以使用暴力搜索的方式,不过经试验下面这种搜索方法POJ会提示 Time Limit Exceeded …
#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;
#define MAX_Y 3
bool mark[MAX_Y][30];
//放置多米诺牌的方向,只需向右或向下搜索
int to[2][2] = {{1, 0},{0, 1}};
int slnCount = 0;
int cell[MAX_Y][30];
int cellIndex = 0;
int cache[30];
void printSolutions(int endX, int endY)
{
cout << "Case #" << slnCount << ":" << endl;
for (int i = 0; i <= endY; i++) {
for (int j = 0; j <= endX; j++) {
if (cell[i][j] == 0) {
cout << setw(4) << "" << "|";
}
else {
cout << setw(4) << cell[i][j] << "|";
}
}
cout << endl;
}
cout << endl << endl;
}
//以当前起点向各方向搜索铺设多米诺牌直到终点
void search(int startX, int startY, int endX, int endY)
{
if (startX == endX && startY == endY) {
//直到搜索成功到最后一点则计数
if (mark[endY][endX]) {
slnCount++;
// printSolutions(endX, endY);
}
return;
}
//选铺设多米诺牌的方向
for (int i = 0; i < 2; i++) {
int nowX = startX + to[i][0];
int nowY = startY + to[i][1];
if ((mark[nowY][nowX] == false) && (nowX > -1 && nowX <= endX) && (nowY > -1 && nowY <= endY)) {
mark[startY][startX] = true;
mark[nowY][nowX] = true;
cell[startY][startX] = cell[nowY][nowX] = ++cellIndex;
//选取下一个起始点,到行末则换行搜,否则向右搜索
bool hasFoundNextPoint = false;
for (int j = startY; j <= endY; j++) {
for (int k = j == startY ? (startX + 1) : 0; k <= endX; k++) {
if (!mark[j][k]) {
hasFoundNextPoint = true;
search(k, j, endX, endY);
break;
}
}
if (hasFoundNextPoint) {
break;
}
}
if (!hasFoundNextPoint) {
//去记数
search(endX, endY, endX, endY);
}
//回溯,这两个位置从未走过
mark[startY][startX] = false;
mark[nowY][nowX] = false;
cell[startY][startX] = cell[nowY][nowX] = --cellIndex;
}
}
}
int main()
{
// freopen("in.txt", "r", stdin);
memset(cache, -1, sizeof(cache));
int n;
cin >> n;
while (n != -1) {
if (n < 0) {
cout << slnCount << endl;
continue;
}
slnCount = 0;
if (cache[n] != -1) {
slnCount == cache[n];
}
else if (n > 1) {
memset(mark, false, sizeof(mark));
cellIndex = 0;
search(0, 0, n - 1, MAX_Y - 1);
}
else if (n == 0) {
slnCount = 1;
}
if (n == 1) {
slnCount = MAX_Y % 2 == 0 ? (MAX_Y / 2) : 0;
}
cout << slnCount << endl;
cache[n] = slnCount;
cin >> n;
}
return 0;
}