排列宝石问题
★问题描述:
现有n种不同形状的宝石,每种n颗,共n*n颗。同一形状的n颗宝石分别具有n种不同的颜色c1,c2,…,cn中的一种颜色。欲将这n*n颗宝石排列成n行n列的一个方阵,使方阵中每一行和每一列的宝石都有n种不同的形状和n种不同颜色。是设计一个算法,计算出对于给定的n,有多少种不同的宝石排列方案。
★算法设计:
对于给定的n,计算不同的宝石排列方案数。
★数据输入:
由文件input.txt给出输入数据。第一行有一个正整数n,0<n<9。
★结果输出:
将计算的宝石排列方案数输入到文件output.txt。
解决
算法设计
在写这道题之前可以先了解一下八皇后问题,了解完后可以发现排列宝石起始是八皇后问题的升级版。
关于八皇后问题的解决思路我参照了这篇博客(https://blog.csdn.net/weixin_43773562/article/details/108022604)
八皇后问题只需要考虑每行每列每对角线只能有一个棋子,而排列宝石问题只是需要考虑每行每列宝石的颜色和形状不能相同。
(但是使用深搜索和回溯时间复杂度会很高,关于改进的思路是使用剪枝,或者可以试一下改变放入宝石的顺序)
具体思路如下
由于这是一个n×n的宝石矩阵,因此我们可以从左到右,从上到下给每颗宝石编号0~n×n-1。
用一个bool二维数组used记录对应初始位置的宝石的使用情况,使用过则置为true,否则为false。这里用二维数组是为了方便比较颜色和形状(假定used矩阵中的宝石每行的形状相同,每列的颜色相同)。
假想有一个n×n的容器,宝石放入的顺序也是从左到右从上到下按顺序放入,用一个int一维数组ans记录从第0个位置放到第n×n-1个位置的宝石的编号。比如n=3时,ans[4]=6表示第四个位置(即容器的第二行第二列)放的是编号为6的宝石。
每次从第0个位置按顺序开始放入,当成功放入到最后一个位置时说明找到一种排列方案。每次查找放入的宝石时都从used矩阵中找到还没用过的宝石放入(for循环从第一个开始遍历到最后一个),放入后检查该放入的宝石是否和已经放入的宝石颜色形状相同,若符合就放入该宝石,并继续寻找下一个位置可以放入的宝石;若不符合就继续从used中找,如果最后还找不到就结束该位置的查找,返回到上一个位置继续查找。
源代码
#include <cstdio>
using namespace std;
#define MAX 10
int n, cut; // n种宝石,cut记录一共有多少种放法
int ans[MAX * MAX]; //一共有n*n个元素,代表n*n块宝石,由0~n*n-1代表
bool used[MAX][MAX] = {false}; //是一个n*n矩阵,记录宝石是否用过,其中同一行的宝石形状相同,同一列的宝石颜色相同;用二维数组是因为方便判断形状颜色是否重复
void put(int k); //填装第k个位置,一共n*n个位置需要装入,这里从0开始顺序装入,每次检查check,当前位置装入的宝石合法则装入下一个位置的宝石put(k+1)
bool check(int k); //检查第k个位置装入的宝石是否合法
int main(void)
{
freopen("in.txt", "r", stdin); //输入重定向
freopen("out.txt", "w", stdout); //输出重定向
while (scanf("%d", &n) != EOF)
{
cut = 0; //重置计数
put(0); //从第一颗宝石开始放置
printf("%d阶宝石矩阵有%d中排列方案。\n", n, cut);
}
//关闭重定向
fclose(stdin);
fclose(stdout);
}
void put(int k) //填装第k个位置,一共n*n个位置需要装入,这里从0开始顺序装入,每次检查check,当前位置装入的宝石合法则装入下一个位置的宝石put(k+1)
{
if (k == n * n) //如果装到n*n,说明所有宝石已经装完,说明找到一种解决方法,cut++,返回
{
++cut;
return;
}
for (int i = 0; i < n * n; ++i) //依次选择装入所有宝石,共n * n个,标号为0~n*n-1
{
//如果该颗宝石已经被使用过,则不合法
if (used[i / n][i % n])
continue;
//若该颗宝石没使用过
ans[k] = i; //当前位置装入第i颗宝石
used[i / n][i % n] = true; //标记第i颗宝石被使用过
if (check(k)) //检查当前位置装入的宝石是否合法
put(k + 1); //装入下一个位置,深度搜索
//当前宝石不合法或者该种情况已经查找完则查找下一颗宝石是否符合
used[i / n][i % n] = false; //恢复第i颗宝石的标记
}
}
bool check(int k) //检查第k个位置装入的宝石是否合法
{
//检查形状和颜色
int r = ans[k] / n, t = ans[k] % n; //第k个位置装入的编号为ans[k]的宝石的used数组的位置坐标,可以用于定位该宝石的形状和颜色
for (int i = 0; i < k; ++i) //这里表示已经装入的k颗宝石宝石(位置0~k-1),检查他们与第k个位置上新装入的宝石是否颜色和形状相同
{
int p = ans[i] / n, q = ans[i] % n; // p,q是第i个位置装入的编号为ans[i]的宝石的used数组的位置坐标
if (k / n == i / n || k % n == i % n) //如果两颗宝石放置的位置在同一行或者同一列就需要检查形状颜色是否相同(同行同列的形状和颜色不能相同)
//检查形状(同一行),颜色(同一列)
if (r == p || t == q)
return false;
}
return true; //检查完所有已装入宝石,合法,返回true
}
运行结果
由in.txt输入如下
1
2
3
4
得到out.txt输出如下
1阶宝石矩阵有1中排列方案。
2阶宝石矩阵有0中排列方案。
3阶宝石矩阵有72中排列方案。
4阶宝石矩阵有6912中排列方案。