蓝桥杯---数独(模拟 || dfs)

时间:2023-05-23 08:36:50

【编程题】(满分33分)

“数独”是当下炙手可热的智力游戏。一般认为它的起源是“拉丁方块”,是大数

学家欧拉于1783年发明的。

如图[1.jpg]所示:6x6的小格被分为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

2011到2013蓝桥杯决赛题

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开始),接着输出一个6x6的字母方阵,表示该解。

解的字母之间用空格分开。

如果找不到任何满足条件的解,则输出“无解”

例如:用户输入:

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++标准)

开始的时候随手写了个代码,但是出乎意料的没有对,因为开始的时候直接按照两个方向开始深搜,但是这样会造成一个问题,就是左上角和右下角的可能就直接不走了,因为回溯的时候就把当时走的还原了,这样明显不适合将整个表格填满,并且最多能填17个,所以开始的时间有点浪费,但是还是记录下来,用作之后的比对用。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#define INF 0x3f3f3f3f
#define Maxsize 100
using namespace std;
string str[6];
char map[6][6]; struct T{
char all[6];
int len=0;
}choose[6][6]; struct noFill{
int row,col;
}notfill[36];//记录没有填充的坐标
int Case=0;
int Count=0;
void display()
{
printf("%d\n",++Case);
for(int i=0; i<6; i++)
{
for(int j=0; j<6; j++)
printf("%c ",map[i][j])<<' ';
printf("\n");
}
}
bool judge(char loca,char ch){//判断改行能否在该位置填充ch
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(str[i][j]==loca&&map[i][j]==ch)
return false;
return true;
}
bool judgeRow(int row,char ch){
for(int i=0;i<6;i++)
if(map[row][i]==ch)
return false;
return true;
}
bool judgeCol(int col,char ch){//判断在该小组里能否在该位置填充ch
for(int i=0;i<6;i++)
if(map[i][col]==ch)
return false;
return true;
}
int xxx=0;
void dfs(int tempcount){
if(tempcount==Count) {
display();
return;
}
int row=notfill[tempcount].row,col=notfill[tempcount].col;
char ch='A';
for(int i=0; i<6; i++) {//遍历该位置可以填充的字符
if( judgeRow(row,ch) && judgeCol(col,ch) && judge(str[row][col],ch)){
map[row][col]=ch;
dfs(tempcount+1);
}
ch++;
// xxx++;
}
map[row][col]='0';
return ;
}
int main(){
for(int i=0; i<6; i++)
cin>>str[i];
int n;
char in[5];
scanf("%d",&n);
memset(map,'0',sizeof(map));
for(int i=0; i<n; i++){
scanf("%s",in);
map[in[0]-'0'][in[1]-'0']=in[2];
}
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(map[i][j]=='0')
notfill[Count].row=i,notfill[Count++].col=j;
dfs(0);
// cout<<xxx<<endl;
return 0;
}

由于在向右向下深搜的时候回溯带来的重新还原的问题,这样,我就直接在开始的时候把没有填上的直接找出来,这样就可以一直深搜下去,即便回溯也可以重新经过该位置对其赋值,结果也就正确了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#define INF 0x3f3f3f3f
#define Maxsize 100
using namespace std;
string str[6];
char map[6][6]; struct T{
char all[6];
int len=0;
}choose[6][6]; struct noFill{
int row,col;
}notfill[36];//记录没有填充的坐标
int Case=0;
int Count=0;
void display()
{
printf("%d\n",++Case);
for(int i=0; i<6; i++)
{
for(int j=0; j<6; j++)
printf("%c ",map[i][j])<<' ';
printf("\n");
}
}
bool judge(char loca,char ch){//判断改行能否在该位置填充ch
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(str[i][j]==loca&&map[i][j]==ch)
return false;
return true;
}
bool judgeRow(int row,char ch){
for(int i=0;i<6;i++)
if(map[row][i]==ch)
return false;
return true;
}
bool judgeCol(int col,char ch){//判断在该小组里能否在该位置填充ch
for(int i=0;i<6;i++)
if(map[i][col]==ch)
return false;
return true;
}
int xxx=0;
void dfs(int tempcount){
if(tempcount==Count) {
display();
return;
}
int row=notfill[tempcount].row,col=notfill[tempcount].col;
char ch='A';
for(int i=0; i<6; i++) {//遍历该位置可以填充的字符
if( judgeRow(row,ch) && judgeCol(col,ch) && judge(str[row][col],ch)){
map[row][col]=ch;
dfs(tempcount+1);
}
ch++;
// xxx++;
}
map[row][col]='0';
return ;
}
int main(){
for(int i=0; i<6; i++)
cin>>str[i];
int n;
char in[5];
scanf("%d",&n);
memset(map,'0',sizeof(map));
for(int i=0; i<n; i++){
scanf("%s",in);
map[in[0]-'0'][in[1]-'0']=in[2];
}
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(map[i][j]=='0')
notfill[Count].row=i,notfill[Count++].col=j;
dfs(0);
// cout<<xxx<<endl;
return 0;
}

感觉太暴力了不太好,所以又在遍历的时候首先处理了一下,在最开始的时候找出可能填在该位置的数找了一下,好像也没有节省多少时间,估计动态更新会好很多,就是每次填充上一个的时候,把该行,该列,改组里原来能填这个数的都删掉,但是好像不是很好处理。

#include <iostream>//这样做由于在回溯的时候把当初赋过的值又还原了,多以不能全部填满
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#define INF 0x3f3f3f3f
#define Maxsize 100
using namespace std;
string str[6];
char map[6][6]; struct T{
char all[6];
int len=0;
}choose[6][6]; struct noFill{
int row,col;
}notfill[36];
int Case=0;
int notfillcount=0;
void display()
{
printf("%d\n",++Case);
for(int i=0; i<6; i++)
{
for(int j=0; j<6; j++)
printf("%c ",map[i][j])<<' ';
printf("\n");
}
}
bool judge(char loca,char ch){
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(str[i][j]==loca&&map[i][j]==ch)
return false;
return true;
}
bool judgeRow(int row,char ch){
for(int i=0;i<6;i++)
if(map[row][i]==ch)
return false;
return true;
}
bool judgeCol(int col,char ch){
for(int i=0;i<6;i++)
if(map[i][col]==ch)
return false;
return true;
}
int xxx=0;
void dfs(int tempcount){
xxx++;
if(tempcount==notfillcount) {
display();
return;
}
int row=notfill[tempcount].row,col=notfill[tempcount].col;
for(int i=0; i<choose[row][col].len; i++) {//遍历该位置可以填充的字符
char ch=choose[row][col].all[i];
if( judgeRow(row,ch) && judgeCol(col,ch) && judge(str[row][col],ch)){
map[row][col]=ch;
dfs(tempcount+1);
}
}
map[row][col]='0';
return ;
}
void getChoose(){
for(int i=0;i<6;i++)
for(int j=0;j<6;j++)
if(map[i][j]=='0'){
notfill[notfillcount].row=i,notfill[notfillcount++].col=j;
char ch='A';
for(int x=0;x<6;x++){
if(judgeRow(i,ch)&&judgeCol(j,ch)&&judge(str[i][j],ch))
choose[i][j].all[ choose[i][j].len++ ]=ch;
ch++;
}
}
}
int main(){
for(int i=0; i<6; i++)
cin>>str[i];
int n;
char in[5];
scanf("%d",&n);
memset(map,'0',sizeof(map));
for(int i=0; i<n; i++){
scanf("%s",in);
map[in[0]-'0'][in[1]-'0']=in[2];
}
getChoose();
dfs(0);
cout<<xxx<<endl;
return 0;
}

网友代码(思路也挺好的,应该比我的更省时间)

上面的一种策略虽在开始的时候对每个位置能填的数先做了一下判断,但是在填数的时候还是不能动态的处理更新问题,下面的这种方式就很好的解决了这个问题,因为他每次都从最好填的位置开始填起的。。。

这个代码感觉运用空间的方式比较好,浪费掉几个“不值钱”的内存,处理起来却比较方便,并且还是运用近乎模拟的方式,每次从每行每列加起来的数字最多的位置开始填充,这样能够更节省时间,但是正常的处理也不会浪费太多时间,蓝桥杯还是几乎不需要优化的

#include<cstdio>//运用打表的优势
#include<iostream>
using namespace std;
int count=0 ; // 表示还剩余几个空没填
char map[6][6]={0};//存储结果,0表示结果矩阵中 当前位置还未填
int row[6]={0},col[6]={0};//统计行,列中元素个数
bool fillGro[6][6]={0}; // fillGrog[i][j] 表示分组为 i 的组中 'A'+j 是否使用过
char group[6][7];//记录输入时的分组情况
int Case = 0 ; //记录结果编号 void outPut(){
printf("%d\n",++Case);
for(int i=0;i<6;i++){
for(int j=0; j<5; j++)
printf("%c ",map[i][j]);
printf("%c\n",map[i][5]);
}
}
void searcher(){
int i,j,k,max,x,y;
bool f[6]={0};
if(count==36){
outPut();
return;
} count++;
max=-1 ;//寻找最佳位置---即当前没有填数的坐标所在行和所在列填数之和最多的位置
for(i=0;i<6;i++){
if(row[i]==6) // 当前行已经填满
continue;
for(j=0;j<6;j++)
if( map[i][j]==0 && (row[i]+col[j])>max ) { //每次找当前坐标所在行和所在列填数之和最多的 先填
max=row[i]+col[j];
x=i;
y=j;
}
}
//寻找所有可以填在最佳位置 (x, y) 的允许值
for(k=0;k<6;k++){
if(map[x][k])
f[ map[x][k]-'A' ] = true ;//
if(map[k][y])
f[ map[k][y]-'A' ] = true ;
if( fillGro[ group[x][y]-'0' ][k] )//k对应的字符(’A‘+k)
f[k]=true ;
}
row[x]++;
col[y]++;//当前的行和列都增加了一个
for(k=0;k<6;k++)
if(f[k]==false){
map[x][y] = 'A'+k;
fillGro[group[x][y]-'0'][k] = true ;
searcher();
fillGro[group[x][y]-'0'][k] = false ; // 回溯
}
// 回溯
row[x]--;//还原添加前的情景
col[y]--;
map[x][y] = 0 ;//还原图中未填状态
count--;
}
int main(){
char c;
int n,i,j;
for(i=0;i<6;i++)
scanf("%s",group[i]);
scanf("%d",&n);
c = getchar();
for( ; n ;n--){
i=getchar()-'0' ;
j=getchar()-'0' ;
c=getchar();//方式新颖
map[i][j] = c ;
count++;
row[i]++;
col[j]++;
fillGro[group[i][j]-'0'][c-'A'] = true ;
getchar();//吸收回车
}
searcher();
return 0;
}

写的有点多,最起码得对得起几个小时走弯路耗费的时间啊啊啊啊啊啊啊啊啊啊啊啊.......