Dancing Links ---- D
In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For example,
. 2 7 3 8 . . 1 . . 1 . . . 6 7 3 5 . . . . . . . 2 9 3 . 5 6 9 2 . 8 . . . . . . . . . . . 6 . 1 7 4 5 . 3 6 4 . . . . . . . 9 5 1 8 . . . 7 . . 8 . . 6 5 3 4 . Given some of the numbers in the grid, your goal is to determine the remaining numbers such that the numbers 1 through 9 appear exactly once in (1) each of nine 3 × 3 subgrids, (2) each of the nine rows, and (3) each of the nine columns.
Input The input test file will contain multiple cases. Each test case consists of a single line containing 81 characters, which represent the 81 squares of the Sudoku grid, given one row at a time. Each character is either a digit (from 1 to 9) or a period (used to indicate an unfilled square). You may assume that each puzzle in the input will have exactly one solution. The end-of-file is denoted by a single line containing the word “end”.
Output For each test case, print a line representing the completed Sudoku puzzle.
Sample Input .2738…1…1…6735…293.5692.8…6.1745.364…9518…7…8…6534. …52…8.4…3…9…5.1…6…2…7…3…6…1…7.4…3. end Sample Output 527389416819426735436751829375692184194538267268174593643217958951843672782965341 416837529982465371735129468571298643293746185864351297647913852359682714128574936
- 每個格子只能填1個數;
- 每行的數字集合為[1, N^2],且不能重複;
- 每列的數字集合為[1, N^2],且不能重複;
- 每個“宮”的數字集合為[1, N^2],且不能重複(其中“宮”的意思就是N×N的格子。對於N=3的情況,就是“九宮格”); 現在問題是給定一個已經填了一些數字的數獨,求當N=3時的一種解,滿足以上四個限制條件。
轉變為精確覆蓋問題。行代表問題的所有情況,列代表問題的約束條件。每個格子能夠填的數字為[1,9],並且總共有9×9(即32×32)個格子,所以總的情況數為729種。也就是DancingLinks的行為729行。 列則分為四種:
- [0, 81)列 分別對應了81個格子是否被放置了數字。
- [82, 2*81)列 分別對應了9行,每行[1, 9]個數字的放置情況;
- [281, 381)列 分別對應了9列,每列[1, 9]個數字的放置情況;
- [381, 481)列 分別對應了9個“宮”,每“宮”[1, 9]個數字的放置情況;
所以總的列數為4*81=324列。如圖五-4-2所示。
舉個例子,對於在數獨棋盤的i行j列的格子(i, j)上放置一個數字k,那麼對應的Dancing Links的01矩陣行,一行上有四個“1”,分別對應四種約束條件:
- 格子限制: 行號*9 + 列號
- 行不重複限制: 81 + 行號 *9 + (k-1)
- 列不重複限制: 2*81 + 列號 *9 + (k-1)
- “宮”不重複限制:3*81 + 宮號 *9 + (k-1) 行號是i,列號是j,比較好理解;那麼宮號我們定義如下圖:
宮號的計算方式可以通過行號和列號得出。即 宮號 = (i/3)*3 + (j/3); 那麼構建01矩陣的時候,我們從上到下,從左到右遍歷數獨,對於在(i, j)上有數字k的只需要插入一行,這行上有四列為“1”。對於沒有填寫數字的需要列舉[1, 9],把在(i, j)位置上填[1, 9]的情況都進行插入,一共9行。 矩陣構建完畢,求一次精確覆蓋即可。
上面的解釋已經非常清晰了。。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
const double eps = 1e-8;
const double PI = acos(-1);
//最大行數
const int MN = 1005;
//最大列數
const int MM = 1005;
//最大點數
const int MNN = 1e5 + 5 + MM;
struct DLX
{
//一共n行m列,s個節點
int n,m,s;
//交叉十字連結串列組成部分
//第i個節點的上U下D左L右R,所在位置row行col列
int U[MNN],D[MNN],L[MNN],R[MNN],row[MNN],col[MNN];
//H陣列記錄行選擇指標,S陣列記錄覆蓋個數
int H[MN],S[MM];
//res記錄行個數,ans陣列記錄可行解
int res,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;
s = m;
memset(S,0,sizeof(S));
memset(H,-1,sizeof(H));
}
void Insert(int r,int c)
{
//節點數加一,設定s節點所處位置,以及S列覆蓋個數加一
s++;row[s] = r;col[s] = c;S[c]++;
//將s節點插入對應列中
D[s] = D[c];U[D[c]] = s;
U[s] = c;D[c] = s;
if(H[r] < 0){//如果該行沒有元素,H[r]標記該行起始節點
H[r] = L[s] = R[s] = s;
}else{
//將該節點插入該行第一個節點後面
R[s] = R[H[r]];
L[R[H[r]]] = s;
L[s] = H[r];
R[H[r]] = s;
}
}
//精確覆蓋
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 dance(int deep)
{
if(res < deep) return false;
//當矩陣為空時,說明找到一個可行解,演算法終止
if(R[0] == 0){
res = 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放入當前解
ans[deep] = row[i];
//行上節點對應的列上進行刪除
for(int j = R[i];j != i;j = R[j])
Remove(col[j]);
//進入下一層
if(dance(deep + 1)) return true;
//對行上的節點對應的列進行恢復
for(int j = L[i];j != i;j = L[j])
resume(col[j]);
}
//恢復節點數最少列
resume(c);
return false;
}
//重複覆蓋
//將列與矩陣完全分開
void Remove1(int c)
{
for(int i = D[c];i != c;i = D[i]){
L[R[i]] = L[i];
R[L[i]] = R[i];
}
}
void resume1(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;
}
bool dfs(int deep)
{
if(deep + A() > res) return false;
if(!R[0]) return true;
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節點,為了找該行的節點
Remove1(i);
//將列上的節點完全與矩陣脫離,只刪列首節點是不行的
for(int j = R[i];j != i;j = R[j]){
Remove1(j);
}
if(dfs(deep + 1)) return true;
for(int j = L[i];j != i;j = L[j]){
resume1(j);
}
resume1(i);
}
return false;
}
int IDEA(int k)
{
res = 0;
while(true)
{
if(res > k) break;
//cout << res << endl;
if(dfs(0)) return res;
res++;
}
return -1;
}
}dlx;
const int N = 105;
char s[N];
int p[10][10];
pair<pair<int,int>,int>ve[805];
void init()
{
int len = strlen(s);
dlx.init(729,4 * 81);
int cnt = 1;
for(int i = 0;i < len;++i){
int x = i / 9,y = i % 9;
if(s[i] != '.'){
int k = s[i] - '0';
p[x][y] = k;
int z = (x / 3) * 3 + (y / 3);
dlx.Insert(cnt,x * 9 + y + 1);
dlx.Insert(cnt,81 + x * 9 + k);
dlx.Insert(cnt,2 * 81 + 9 * y + k);
dlx.Insert(cnt,3 * 81 + 9 * z + k);
ve[cnt].fi = mp(x,y);ve[cnt].se = k;
cnt++;
}else{
int z = (x / 3) * 3 + (y / 3);
for(int j = 1;j <= 9;++j){
dlx.Insert(cnt,x * 9 + y + 1);
dlx.Insert(cnt,81 + x * 9 + j);
dlx.Insert(cnt,2 * 81 + 9 * y + j);
dlx.Insert(cnt,3 * 81 + 9 * z + j);
ve[cnt].fi = mp(x,y);ve[cnt].se = j;
cnt++;
}
}
}
dlx.res = inf;
dlx.dance(0);
//cout << dlx.res << endl;
for(int i = 0;i < dlx.res;++i){
p[ve[dlx.ans[i]].fi.fi][ve[dlx.ans[i]].fi.se] = ve[dlx.ans[i]].se;
}
for(int i = 0;i < 9;++i){
for(int j = 0;j < 9;++j){
printf("%d",p[i][j]);
}
}
printf("\n");
}
int main()
{
while(~scanf("%s",s))
{
if(strcmp(s,"end") == 0){
break;
}
init();
}
return 0;
}