POJ 2663 Tri Tiling(完美覆盖)

描述

一张普通的国际象棋棋盘,它被分成 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;
}
  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值