题目:
数独是一种填数字游戏,英文名叫 Sudoku。
玩家需要根据 9×9盘面上的已知数字,推理出所有剩余位置的数字,并满足每一行、每一列、每一个粗线九宫格内的数字包含有 1-9 的数字,且不重复。
输入描述:
多测试用例,输入T表示测试用例个数。
输入每一组数据,共 9 行 9 列,表示初始数独(其中 0 表示数独中的空位)。
输出描述:
输出共 9 行 9 列,表示数独的解。
Sample Input:
1
103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107
Sample Output:
143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127
代码如下:
hang[ i ][ j ]表示第i行的数j是否已填,lie[ i ][ j ]表示第i列的数j是否已填,dyg[ i ][ j ]表示第i个3×3单元格的数j是否已填。
/*140ms*/
#include<cstdio>
#include<cstring>
using namespace std;
int G[10][10];
bool hang[10][10],lie[10][10],dyg[10][10],flag;///hang表示行,lie表示列,dyg表示3×3的单元格
void _dfs(int x,int y)///x为行,y为列
{
if(flag)return;
if(x==9){ ///全部搜完
flag=1;
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
printf("%d",G[i][j]);
}
printf("\n");
}
return;
}
else{
if(G[x][y]){///该位置已经有数字的情况
if(y<8)_dfs(x,y+1);///未到最后一列,则继续搜索下一列
else _dfs(x+1,0);///已到最后一列,则搜索下一行的第一列
}
else{///该位置没有数字的情况
for(int i=1;i<=9;i++){
if(!hang[x][i]&&!lie[y][i]&&!dyg[x/3*3+y/3][i]){
G[x][y]=i;///保存搜到的数
hang[x][i]=lie[y][i]=dyg[x/3*3+y/3][i]=true;///标记
if(y<8)_dfs(x,y+1);///未到最后一列,则继续搜索下一列
else _dfs(x+1,0);///已到最后一列,则搜索下一行的第一列
G[x][y]=0;///回溯,该位置重新标为没有放入数字
hang[x][i]=lie[y][i]=dyg[x/3*3+y/3][i]=false;///回溯
}
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
/*初始化*/
memset(hang,false,sizeof(hang));
memset(lie,false,sizeof(lie));
memset(dyg,false,sizeof(dyg));
flag=0;
/*输入初始数独*/
for(int i=0;i<9;i++)
for(int j=0;j<9;j++){
scanf("%1d",&G[i][j]);///输入没有包含空格,所有要用到%1d这个操作
if(G[i][j]!=0){
int v=G[i][j];
hang[i][v]=true;
lie[j][v]=true;
dyg[i/3*3+j/3][v]=true;///或者可以用个三维数组dyg[i/3][j/3][v]来标记3×3的单元格
}
}
_dfs(0,0);///第1行第1列
}
return 0;
}
E.数独挑战——2019年华南理工大学程序设计竞赛(春季赛):
链接:https://ac.nowcoder.com/acm/contest/625/E 单测试用例,题意与上述题目相同。
输入样例:
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9
输出样例:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9
以下做法不用另开几个标记数组,但运行时间慢于上面那份代码,代码如下:
#include<bits/stdc++.h>
using namespace std;
int G[10][10],flag;
bool check(int x,int y,int k)
{
for(int i=0;i<9;i++)
if(G[x][i]==k)return false;///该行有重复的数
for(int j=0;j<9;j++)
if(G[j][y]==k)return false;///该列有重复的数
int a=x/3*3,b=y/3*3;///先除3是计算属于哪个宫内的,再乘3是计算该宫第一个数的下标
for(int i=a;i<a+3;i++)
for(int j=b;j<b+3;j++)
if(G[i][j]==k)return false;///该3*3宫内有重复
return true;
}
void dfs(int x,int y)
{
if(flag) return;
if(x==9&&y==0){///完整搜到了最后一行最后一列,特别注意搜完最后一列后是dfs(9,0)即跳到了第10行的第1列
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
printf("%d ",G[i][j]);
}
puts("");
}
flag = 1;
return;
}
if(y>8)dfs(x+1,0);///到达最后一列,搜索下一行
else{///未到最后一列的情况
if(G[x][y])dfs(x,y+1);///若该位置已经有数字,则继续下一列
else{///该位置没数字的情况
for(int k=1;k<=9;k++){
if(check(x,y,k)){
G[x][y]=k;///保存搜到的数
dfs(x,y+1);///继续搜索下一列
G[x][y]=0;///回溯,把该格重新标为0即没有放入数字
}
}
}
}
}
int main()
{
flag=0;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
scanf("%d",&G[i][j]);
dfs(0,0);///从第1行第1列开始搜
return 0;
}
数独的舞蹈链(Dancing Links)做法:
(比dfs快很多,贴一下别人代码,待以后学习)
#include <cassert>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iterator>
#include <ctime>
#include <algorithm>
#include <bitset>
#include <deque>
#include <iostream>
#include <iomanip>
#include <limits>
#include<functional>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>
#include <numeric>
//#include<unordered_map>
#define inf 2e9
#define seed 131
#define pi acos(-1)
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) ((x)&(-x))
#define lrtnb srand(unsigned(time(NULL)))
#define lrtlh ios::sync_with_stdio(0)
#define lowb(a,n,x) lower_bound(a,a+n,x) - a
#define uppb(a,n,x) upper_bound(a,a+n,x) - a
#define lson l,mid,i<<1;
#define rson mid+1,r,i<<1|1;
#define ALL(x) x.begin(),x.end()
#define inv inline void
#define INS(x) inserter(x,x.begin())
using namespace std;
typedef long long ll;
typedef long double ld;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef map<int, int> mii;
typedef map<ll, ll> mll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef stack<int> si;
typedef stack<ll> sl;
typedef set<int> Si;
typedef set<ll> Sl;
ll gcd(ll n, ll m) { long long r; if (n < m)swap(n, m); while (m) { r = n % m; n = m; m = r; }return n; }
ll lcm(ll b, ll y) { return b * y / gcd(b, y); }
double das(double x1, double y1, double x2, double y2) { double ans = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2); return sqrt(ans); }
double random(double start, double en) { return start + (en - start)*rand() / (RAND_MAX + 1.0); }
inline void read(int &n) { char c = '+'; int x = 0; bool flag = 0; while (c<'0' || c>'9') { c = getchar(); if (c == '-')flag = 1; }while (c >= '0'&&c <= '9')x = x * 10 + c - 48, c = getchar(); n = flag == 1 ? -x : x; }
//舞蹈链
//最大行数
const int MN = 1010;
//最大列数
const int MM = 500;
//最大点数
const int MNN = MN * MM + 10;
struct DLX
{
//一共n行m列,s个节点
int n, m, size;
//交叉十字链表组成部分
//第i个节点的上U下D左L右R,所在位置row行col列
int U[MNN], D[MNN], L[MNN], R[MNN], row[MNN], col[MNN];
//H数组记录行选择指针,S数组记录覆盖个数
int H[MNN], S[MM];
//res记录行个数,ans数组记录可行解
int ansd, ans[MN];
//初始化空表
void init(int x, int y)
{
n = x, m = y;
//其中0节点作为head节点,其他作为列首节点
for (int i = 0; i <= m; ++i) {
U[i] = D[i] = i;
L[i] = i - 1;
R[i] = i + 1;
}
R[m] = 0; L[0] = m;
size = m;
memset(S, 0, sizeof(S));
memset(H, -1, sizeof(H));
}
void Link(int r, int c)
{
//节点数加一,设置s节点所处位置,以及S列覆盖个数加一
size++; row[size] = r; col[size] = c; S[c]++;
//将s节点插入对应列中
D[size] = D[c]; U[D[c]] = size;
U[size] = c; D[c] = size;
if (H[r] < 0) {//如果该行没有元素,H[r]标记该行起始节点
H[r] = L[size] = R[size] = size;
}
else {
//将该节点插入该行第一个节点后面
R[size] = R[H[r]];
L[R[H[r]]] = size;
L[size] = H[r];
R[H[r]] = size;
}
}
//精确覆盖
void remove(int c)
{
//删除c列
L[R[c]] = L[c]; R[L[c]] = R[c];
//删除该列上的元素对应的行
for (int i = D[c]; i != c; i = D[i]) {//枚举该列元素
for (int j = R[i]; j != i; j = R[j]) {//枚举列的某个元素所在行遍历
U[D[j]] = U[j];
D[U[j]] = D[j];
//将该列上的S数组减一
--S[col[j]];
}
}
}
void resume(int c)
{
//恢复c列
for (int i = U[c]; i != c; i = U[i]) {//枚举该列元素
for (int j = L[i]; j != i; j = L[j]) {
U[D[j]] = j; D[U[j]] = j;
++S[col[j]];
}
}
L[R[c]] = c; R[L[c]] = c;
}
bool dancing(int deep)
{
//if (ansd < deep) return false;
//当矩阵为空时,说明找到一个可行解,算法终止
if (R[0] == 0) {
//ansd = min(ansd, deep);
ansd = deep;
return true;
}
//找到节点数最少的列,枚举这列上的所有行
int c = R[0];
for (int i = R[0]; i != 0; i = R[i]) {
if (S[i] < S[c]) {
c = i;
}
}
//删除节点数最少的列
remove(c);
for (int i = D[c]; i != c; i = D[i]) {
//将行r放入当前解
//行上节点对应的列上进行删除
for (int j = R[i]; j != i; j = R[j])
remove(col[j]);
ans[deep] = row[i];
//进入下一层
if (dancing(deep + 1))return true;
//对行上的节点对应的列进行恢复
for (int j = L[i]; j != i; j = L[j])
resume(col[j]);
}
//恢复节点数最少列
resume(c);
return false;
}
//重复覆盖
//将列与矩阵完全分开
void Remove(int c)
{
for (int i = D[c]; i != c; i = D[i]) {
L[R[i]] = L[i];
R[L[i]] = R[i];
}
}
void Resume(int c)
{
for (int i = D[c]; i != c; i = D[i]) {
L[R[i]] = R[L[i]] = i;
}
}
int vis[MNN];
//估价函数,模拟删除列,H(),函数返回的是至少还需要多少行才能完成重复覆盖
int A()
{
int dis = 0;
for (int i = R[0]; i != 0; i = R[i]) vis[i] = 0;
for (int i = R[0]; i != 0; i = R[i]) {
if (!vis[i]) {
dis++; vis[i] = 1;
for (int j = D[i]; j != i; j = D[j]) {
for (int k = R[j]; k != j; k = R[k]) {
vis[col[k]] = 1;
}
}
}
}
return dis;
}
void dance(int deep)
{
if (!R[0]) {
//cout << res << endl;
ansd = deep;
return;
}
if (deep + A() >= ansd) return;
int c = R[0];
for (int i = R[0]; i != 0; i = R[i]) {
if (S[i] < S[c]) {
c = i;
}
}
for (int i = D[c]; i != c; i = D[i]) {
//每次将第i列其他节点删除,只保留第i节点,为了找该行的节点
Remove(i);
//将列上的节点完全与矩阵脱离,只删列首节点是不行的
for (int j = R[i]; j != i; j = R[j]) {
Remove(j);
}
dance(deep + 1);
for (int j = L[i]; j != i; j = L[j]) {
Resume(j);
}
Resume(i);
}
}
};
DLX dzl;
int mp[15][15];
int x[1010];
int y[1010];
int kk[1010];
int main() {
int s[15][15];
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++)
cin >> mp[i][j];
}
dzl.init(800, 324);
int len = 1;
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (mp[i][j] == 0) {
for (int k = 1; k <= 9; k++) {
dzl.Link(len, (i - 1) * 9 + j);
dzl.Link(len, (i - 1) * 9 + k + 81);
dzl.Link(len, (j - 1) * 9 + k + 162);
dzl.Link(len, (((i - 1) / 3) * 3 + ((j + 2) / 3) - 1) * 9 + k + 243);
x[len] = i, y[len] = j, kk[len] = k;
len++;
}
}
else {
int k = mp[i][j];
dzl.Link(len, (i - 1) * 9 + j);
dzl.Link(len, (i - 1) * 9 + k + 81);
dzl.Link(len, (j - 1) * 9 + k + 162);
dzl.Link(len, (((i - 1) / 3) * 3 + ((j + 2) / 3) - 1) * 9 + k + 243);
x[len] = i, y[len] = j, kk[len] = k;
len++;
}
}
}
dzl.dancing(0);
//cout << dzl.ansd << endl;
for (int i = 0; i < dzl.ansd; i++) {
int r = dzl.ans[i];
mp[x[r]][y[r]] = kk[r];
}
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <9; j++) {
cout << mp[i][j]<<" ";
}
cout << mp[i][9] << endl;;
}
system("pause");
}