【编程题】(满分33分)
“数独”是当下炙手可热的智力游戏。一般认为它的起源是“拉丁方块”,是大数学家欧拉于1783年发明的。
如图[1.jpg]所示:6×6的小格被分为6个部分(图中用不同的颜色区分),每个部分含有6个小格(以下也称为分组)。
开始的时候,某些小格中已经填写了字母(ABCDEF之一)。需要在所有剩下的小格中补填字母。
全部填好后,必须满足如下约束:
1. 所填字母只允许是A,B,C,D,E,F 中的某一个。
2. 每行的6个小格中,所填写的字母不能重复。
3. 每列的6个小格中,所填写的字母不能重复。
4. 每个分组(参见图中不同颜色表示)包含的6个小格中,所填写的字母不能重复。
为了表示上的方便,我们用下面的6阶方阵来表示图[1.jpg]对应的分组情况(组号为0~5):
000011
022013
221113
243333
244455
445555
用下面的数据表示其已有字母的填写情况:
02C
03B
05A
20D
35E
53F
很明显,第一列表示行号,第二列表示列号,第三列表示填写的字母。行号、列号都从0开始计算。
一种可行的填写方案(此题刚好答案唯一)为:
E F C B D A
A C E D F B
D A B E C F
F B D C A E
B D F A E C
C E A F B D
你的任务是:编写程序,对一般的拉丁方块问题求解,如果多解,要求找到所有解。
【输入、输出格式要求】
用户首先输入6行数据,表示拉丁方块的分组情况。
接着用户输入一个整数n (n<36), 表示接下来的数据行数
接着输入n行数据,每行表示一个预先填写的字母。
程序则输出所有可能的解(各个解间的顺序不重要)。
每个解占用7行。
即,先输出一个整数,表示该解的序号(从1开始),接着输出一个6×6的字母方阵,表示该解。
解的字母之间用空格分开。
如果找不到任何满足条件的解,则输出“无解”
例如:用户输入:
000011
022013
221113
243333
244455
445555
6
02C
03B
05A
20D
35E
53F
则程序输出:
1
E F C B D A
A C E D F B
D A B E C F
F B D C A E
B D F A E C
C E A F B D
再如,用户输入:
001111
002113
022243
022443
544433
555553
7
04B
05A
13D
14C
24E
50C
51A
则程序输出:
1
D C E F B A
E F A D C B
A B F C E D
B E D A F C
F D C B A E
C A B E D F
2
D C E F B A
E F A D C B
A D F B E C
B E C A F D
F B D C A E
C A B E D F
3
D C F E B A
A E B D C F
F D A C E B
B F E A D C
E B C F A D
C A D B F E
4
D C F E B A
B E A D C F
A D C F E B
F B E A D C
E F B C A D
C A D B F E
5
D C F E B A
E F A D C B
A B C F E D
B E D A F C
F D B C A E
C A E B D F
6
D C F E B A
E F A D C B
A B D F E C
B E C A F D
F D B C A E
C A E B D F
7
D C F E B A
E F A D C B
A D B F E C
B E C A F D
F B D C A E
C A E B D F
8
D C F E B A
F E A D C B
A D B C E F
B F E A D C
E B C F A D
C A D B F E
9
D C F E B A
F E A D C B
A F C B E D
B D E A F C
E B D C A F
C A B F D E
【注意】
请仔细调试!您的程序只有能运行出正确结果的时候才有机会得分!
在评卷时使用的输入数据与试卷中给出的实例数据可能是不同的。
请把所有函数写在同一个文件中,调试好后,拷贝到【考生文件夹】下对应题号的“解答.txt”中即可。
相关的工程文件不要拷入。
源代码中不能使用诸如绘图、Win32API、中断调用、硬件操作或与操作系统相关的API。
允许使用STL类库,但不能使用MFC或ATL等非ANSI c++标准的类库。
例如,不能使用CString类型(属于MFC类库);例如,不能使用randomize, random函数(不属于ANSI C++标准)
分析:
本题思路很简单,就是dfs暴搜,但是如果只是单纯的暴搜,在最后的时候再进行每行每列和每组的重复性判断,那么程序的效率会变得非常低,这时候就需要进行剪枝。对于剪枝,也是有很多方法的,首先dfs是通过对6*6的矩阵从左到右,从上到下的对每个方格依次放入’A’~’F’六个字母,这样的话,我们是先行遍历,所以每一行结束可以进行行的重复性判断,然后最后在6*6个方格全部放入字母后,进行列和组的重复性判断。但是这个剪枝方法,还是比较低效的。
继续优化的话,我们使用三个数组usedlie,usedhang,usedzu来分别存放每列,行,组中已经使用的字母,具体例如usedlie[i][j]表示第i列的第j个字母(即’A’+j)已经使用了,这样我们就可以在每次对方格中的数据进行赋值时,都进行行,列,组的非重复性选择。这种方法,可以大大提高程序的效率。
此外,本题还有几个小技巧:
1.我们并不是直接将字母’A’~’F’放入方格,而是放入数字0~5,因为对于数字的操作显然比操作字符简单一些,最后在输出的时候,只需要 数字+’A’即可。
2.对于组的存放,并不像行列一样直接通过方格的位置横纵座标就能获得,我们在输入的时候,就将属于第i组的方格[x][y]经过矩阵展开后,将结果放在zu[i]中,也就是说zu[i][j]=x*6+y表示座标为[x][y]的方格数组第i组,且是其第j个元素。(但是这个技巧是在上述提到的最后进行组重复性判断下使用,便于快速找到每组的方格,而在使用usedzu进行重复性判断的时候,该数组就不需要了)
3.对于初始化已经放入字母的方格,我们可以使用already数组,already[i][j]=1就表示指定的方格中已经放入了字母,这样,在进行dfs遍历到该方格时,直接跳过遍历下一个方格即可。
以下即为源码:
#include<iostream>
#include<string.h>
using namespace std;
//int zu[6][6]; //zu[i][j]表示第i组的第j个位置的下标转换结果
//int pos[6]={0}; //pos[i]表示下一个第i组的位置放在zu[i]中的位置,即zu[i][pos[i]]
//char test[7][7]={"EFCBDA","ACEDFB","DABECF","FBDCAE","BDFAEC","CEAFBD"};
int res[6][6]; //res表示0~5数字形式的结果
bool already[6][6]; //already表示指定位置的元素已经被赋值
int count=0; //count表示已找到的结果数
bool usedlie[6][6]; //used[i][j]表示下标为i的列是否已经使用了数字j
bool usedhang[6][6]; //used[i][j]表示下标为i的行是否已经使用了数字j
bool usedzu[6][6];
int zuadj[6][6]; //zuadj表示指定位置的元素所在的组编号
void print()
{
cout<<++count<<endl;
if(count%10==0) char c=cin.get();
for(int i=0;i<6;i++)
{
for(int j=0;j<6;j++)
{
cout<<char(res[i][j]+'A')<<" ";
}
cout<<endl;
}
cout<<endl;
}
void dfs(int x,int y)
{
if(x==6 && y==0) // 方格填数结束 ,而在之前通过 usedlie,usedhang,usedzu已经判断过行列组的元素重复性,这里直接输出即可
{
print();
return;
}
int nextx=x;
int nexty=y+1;
if(nexty>5)
{
nextx++;
nexty=0;
}
if(already[x][y]==true)
{
dfs(nextx,nexty);
}
else{
for(int i=0;i<6;i++)
{
if(usedlie[y][i]==false && usedhang[x][i]==false && usedzu[zuadj[x][y]][i]==false)
{
res[x][y]=i;
usedlie[y][i]=true;
usedhang[x][i]=true;
usedzu[zuadj[x][y]][i]=true;
dfs(nextx,nexty);
usedlie[y][i]=false;
usedhang[x][i]=false;
usedzu[zuadj[x][y]][i]=false;
}
}
}
}
int main()
{
for(int i=0;i<6;i++)
{
string str="";
cin>>str;
for(int j=0;j<6;j++)
{
int temp=str[j]-'0';
zuadj[i][j]=temp;
//zu[temp][pos[temp]++]=i*6+j;
}
}
int N;
cin>>N;
for(int i=0;i<N;i++)
{
string str="";
cin>>str;
int x=str[0]-'0';
int y=str[1]-'0';
int value=str[2]-'A';
res[x][y]=value;
already[x][y]=true;
usedlie[y][value]=true;
usedhang[x][value]=true;
usedzu[zuadj[x][y]][value]=true;
}
dfs(0,0);
return 0;
}
PS:虽然拿到这题就有了思路-dfs加剪枝,但是代码实现起来却着实浪费了我不少时间,尤其是在剪枝的优化时,代码也是不断调试了好长时间。但是,说实话,这样不断调试,一点点优化,最后有结果的感觉,真是棒棒哦~代码也是写的酣畅淋漓~