问题描述
有一天,盾神接触到了风靡世界的小游戏——数独!!!盾神非常感兴趣,不惜翘课用了一天的时间把数独玩得出神入化!!!于是他要过来考考你。听到你“雅蠛蝶”的叫声,盾神心软了,于是把经典的99的棋盘缩小到44的。
输入格式
输入为4*4的矩阵,如果第i行第j列为0,则该格子未填数;否则该格子已经有数。规则和原来一样,只是要填的数变成从1到4。于是你会做了。
输出格式
输出为若干个4*4的矩阵,表示所有方案,按字典序升序排列。在最后新起一行输出#表示输出结束。方案之间,方案与#之间空一行。
矩阵大小关系的定义:第一关键字为a[1][1],第二关键字为a[1][2],……第四关键字为a[1][4],第五关键字为a[2][1],以此类推。矩阵A小于矩阵B,当且仅当存在k,A和B的前k-1个关键字的值都相等,而A的第k个关键字的值小于B的第k个关键字的值。矩阵A等于矩阵B,当且仅当A小于B和B小于A都不成立。
字典序升序的定义:在矩阵序列a中,对于任意的i<=j,有a[i]<=a[j]。
样例输入
我在提交之后发现本题的意思是将所有的情况全部输出出来,并且按照字典序输出。那我给出一个提交之后系统提供的一个样例输入。
1 2 3 4
3 4 1 2
0 0 0 0
0 0 0 0
样例输出
1 2 3 4
3 4 1 2
2 1 4 3
4 3 2 1
1 2 3 4
3 4 1 2
2 3 4 1
4 1 2 3
1 2 3 4
3 4 1 2
4 1 2 3
2 3 4 1
1 2 3 4
3 4 1 2
4 3 2 1
2 1 4 3
#
代码
代码实现的思想过程我就不赘述了,相应的注释我也在代码中进行了标注。
本题的要求的要求是要按照字典序的大小依次进行输出,从小到大排列,我们只需要填充的数字是从1开始,到4结束的即可,因为我们填入的数是按照从小到大的顺序排列的,那么我们依次回溯时得到的所有结果也都是从小到大的。
#include <iostream>
using namespace std;
int mp[5][5];
int vx[5][5] = { 0 }; //判断在一行中是否出现相同的数字
int vy[5][5] = { 0 }; //判断在一列中是否出现相同的数字
int vis[5][5] = { 0 }; //判断在一个区间块(在本题中将4*4划分成4个2*2的小格子)中是否出现相同的数字
bool f = false; //如果为true,代表4*4方格已经填满,需要进行回溯找寻其他的可能
//如果为false,就控制回溯是按一级一级来的,
//将每一级的位置上的所有情况输出后,在逐上一级去延续此操作,直至所有情况找完
int Ans = 0; //代表有解,将所有情况输出后,以“#”结尾
void dfs_simple(int row, int col)
{
//如果为true,返回上一个位置
//再根据上一个位置的状态,去探索另一种情况
if (f)
{
return;
}
//代表此列已经走到尽头了,需要换行
if (col == 4)
{
dfs_simple(row + 1, 0);
}
//代表此行已经走到尽头了,需要输出结果
//并且将Ans置为1,
//将f置为true
if (row == 4)
{
f = true;
Ans++;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
cout << mp[i][j] << " ";
}
cout << endl;
}
cout << endl;
return;
}
//代表此时的位置可以填数
if (mp[row][col] == 0)
{
//将数字从1开始遍历,可以保证最后输出的所有可能情况是按照字典序最小的情况输出的。
for (int i = 1; i <= 4; i++)
{
//判断此行,此列,此2*2小格子内是否存在数字i
if (!vx[row][i] && !vy[col][i] &&
!vis[((row / 2) * 2) + col / 2][i])
{
//满足条件,将此行,此列,此2*2小格子内数字i置为true,
//表示此刻它的状态是已经被标记了
vx[row][i] = true;
vy[col][i] = true;
vis[((row / 2) * 2) + col / 2][i] = true;
//在此位置填入数字i
mp[row][col] = i;
//在这个位置填过数字之后,开始回归它的下一个位置
dfs_simple(row, col + 1);
//这个地方是一个重点,关系你能不能将所有情况能够输出。
//当我们开始回溯时,我们要将f置为false,确保能够一级一级的回溯
//在回溯的每一级中都要将所有情况都探寻一遍
//也就是回溯的每一级的位置上放置不同满足条件的数字,再去探寻别的路径,然后进行输出
f = false;
//在我们开始回溯时,我们将此刻位置的状态全部清空
//通俗来讲,就是你上面的这种情况用了,此时你想在换种情况再试试,那么就要将占住的位置给腾出来
vx[row][i] = false;
vy[col][i] = false;
vis[((row / 2) * 2) + col / 2][i] = false;
mp[row][col] = 0;
}
}
}
else //此时的位置已经有数,往下一个位置去走
{
dfs_simple(row, col + 1);
}
}
int main()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
cin >> mp[i][j];
}
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
// 将此行,此列,此2*2小格子内数字有重复,那么就不进行递归,直接输出“#”
if (vx[i][mp[i][j]] || vy[j][mp[i][j]] || vis[((i / 2) * 2) + j / 2][mp[i][j]])
{
cout << "#" << endl;
return 0;
}
//将此刻位置有此数字的此行,此列,此2*2小格子内对应位置置为true,代表标记。
if (mp[i][j] != 0)
{
vx[i][mp[i][j]] = true;
vy[j][mp[i][j]] = true;
vis[((i / 2) * 2) + j / 2][mp[i][j]] = true;
}
}
}
dfs_simple(0, 0);
// 代表有解,输出“#”代表以上所有情况都已搜索完成
if (Ans != 0)
{
cout << "#" << endl;
}
return 0;
}
总结
这道题的难点就在于要将所有的可能情况进行输出。在我们输出第一个结果时,我们再回溯的过程中要去加以控制,在本题当中就是在回溯时使 f = false,这样就确保了代码在回溯时能够按照一个层级一个层级的方式进行回溯,直至所有的结果全部输出。