POJ 2676 Sudoku
Sudoku
Sudoku is a very simple task. A square table with 9 rows and 9 columns is divided to 9 smaller squares 3x3 as shown on the Figure. In some of the cells are written decimal digits from 1 to 9. The other cells are empty. The goal is to fill the empty cells with decimal digits from 1 to 9, one digit per cell, in such way that in each row, in each column and in each marked 3x3 subsquare, all the digits from 1 to 9 to appear. Write a program to solve a given Sudoku-task.
Input
The input data will start with the number of the test cases. For each test case, 9 lines follow, corresponding to the rows of the table. On each line a string of exactly 9 decimal digits is given, corresponding to the cells in this line. If a cell is empty it is represented by 0.
Output
For each test case your program should print the solution in the same format as the input data. The empty cells have to be filled according to the rules. If solutions is not unique, then the program may print any one of them.
Sample
Input
1
103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107
Output
143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127
题目大意
- 就是普通的数独游戏,没有任何其它的特殊要求
- 即每一行,每一列,每一个 3 × 3 3 \times3 3×3 的小格子里面都是从1到9的9个数字而不重复
-
3
×
3
3\times3
3×3 的小格子指的是下面图片中的加粗黑线分割而成的小格子,而不是任意的
3
×
3
3\times3
3×3 小格子
思路
- 关于这道题,这里记录一种普通的DFS解法
- 算法题里的所谓“时空互换”:如果你的程序要跑得快,那么所用的空间就多,所谓空间换时间
- 这里并没有去追求极致的“0 ms AC”,而只是用了一种普通的时间和空间都还过得去的方法
- 既然是DFS,那么肯定要考虑状态转移,这里显然用“没有填数的位置”作为DFS函数的参数较好,那么考虑状态转移,就要知道“下一个没有填数的位置”,所以首先要预处理,把每一个没填数位置的“下一个没填数位置”提前先找出来,之后直接DFS即可
- 所以数组不仅仅是存数独字面上的数,还要存“位置”,所以可以设一个结构体数组,包含字面值和“下一个位置”
- 然后最重要的是判断这个位置到底该填什么数,这里是超不超时的关键
- 这里采用的是,到一个位置后,看看这个位置能填什么数,再遍历这些数,这样其实就减弱了判断,而仅仅是DFS而已了
- 怎么看这个位置能填什么数呢?
- 用一个布尔数组,记录这个位置所在的行、列、小方格的所有数哪些数出现过,出现过的话就置1,之后枚举的时候就跳过已经出现过的数,那么枚举到的数就一定是合法的
代码如下
#include <cstdio>
using namespace std;
struct {
char val;//数独字面值
int n_x, n_y;//下一个为0的位置坐标
} a[9][9];
const int MAXN = 9;
int res;//有多少个需要填数的位置,当这个值为0的时候表示数独已经全部填完
//找出某个位置的填数情况
void cal_sel(bool sel[], int x, int y) {
int hx = (x / 3) * 3;//这个位置所在小方格的左上角横坐标
int hy = (y / 3) * 3; //这个位置所在小方格的左上角纵坐标
//遍历小方格,出现过的数置1
for (int i = hx; i < hx + 3; i++) {
for (int j = hy; j < hy + 3; j++) {
sel[a[i][j].val - '0'] = true;
}
}
//遍历所在行,出现过的数置1
for (int i = 0; i < MAXN; i++) {
sel[a[x][i].val - '0'] = true;
}
//遍历所在列,出现过的数置1
for (int i = 0; i < MAXN; i++) {
sel[a[i][y].val - '0'] = true;
}
}
//DFS
void dfs(int x, int y) {
//如果数独已经填完了,那么就打印出结果并返回
if (res == 0) {
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j < MAXN; j++) {
printf("%c", a[i][j].val);
}
printf("\n");
}
return;
}
//得出这个位置的填数情况(能填哪些数)
bool sel[10] = {false};
cal_sel(sel, x, y);
char tt = a[x][y].val;//保留这个位置的字符,回溯的时候会用到
//从1到9挨个试
for (int i = 1; i <= MAXN; i++) {
if (sel[i]) continue;//不能填的直接过掉
a[x][y].val = i + '0';//能填的话就填上
res--;//空位减一个
dfs(a[x][y].n_x, a[x][y].n_y);//dfs下一个填数的位置
if (res == 0) return;//dfs退出之后发现res为0的话,表示已经找到了可行的答案,可以直接返回,如果去掉这一行,表示找出所有可行的答案
res++;//回溯
}
a[x][y].val = tt;//这一行要写在外面,否则若第一层DFS都不符合的话就会出错
}
void init() {
int i = 0, j = 0;
res = 0;
for (i = 0; i < MAXN; i++) {
for (j = 0; j < MAXN; j++) {
scanf(" %c", &a[i][j].val);
if (a[i][j].val == '0') res++;
}
}
//这里是计算出每个为0的位置的下一个为0的位置,最后一个为0的位置没有下一个为0的位置,所以置为(-1,-1)
int x = -1, y = -1;
for (i = MAXN - 1; i >= 0; i--) {
for (j = MAXN - 1; j >= 0; j--) {
if (a[i][j].val == '0') {
a[i][j].n_x = x;
a[i][j].n_y = y;
x = i, y = j;
}
}
}
}
void solve() {
int n = 0;
bool is_ok = false;
while (~scanf("%d", &n)) {
while (n--) {
init();
//这里的二重循环是找第一个为0的位置,找到后就dfs,dfs完了之后直接退出
is_ok = false;
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j < MAXN; j++) {
if (a[i][j].val == '0') {
dfs(i, j);
is_ok = true;
break;
}
}
if (is_ok) break;
}
}
}
}
int main(void) {
solve();
return 0;
}