Sudoku POJ - 3074 DLX,不重复覆盖,9*9数独

In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For example,

.2738..1.
.1...6735
.......29
3.5692.8.
.........
.6.1745.3
64.......
9518...7.
.8..6534.

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

题意:给出一个9*9的数独,求解;

思路:优先使用的是dlx算法,不过有一点改变。首先看如何转换成dlx算法:

那利用舞蹈链(Dancing Links)算法求解数独问题,实际上就是下面一个流程

1、把数独问题转换为精确覆盖问题

2、设计出数据矩阵

3、用舞蹈链(Dancing Links)算法求解该精确覆盖问题

4、把该精确覆盖问题的解转换为数独的解

 

首先看看数独问题(9*9的方格)的规则

1、每个格子只能填一个数字

2、每行每个数字只能填一遍

3、每列每个数字只能填一遍

4、每宫每个数字只能填一遍

 

那现在就是利用这个规则把数独问题转换为精确覆盖问题

可是,直观上面的规则,发现比较难以转换为精确覆盖问题。因此,把上面的表述换个说法

1、每个格子只能填一个数字

2、每行1-9的这9个数字都得填一遍(也就意味着每个数字只能填一遍)

3、每列1-9的这9个数字都得填一遍

4、每宫1-9的这9个数字都得填一遍

 

这样理解的话,数独问题转换为精确覆盖问题就相对简单多了。关键就是如何构造精确覆盖问题中的矩阵

 

我们把矩阵的每个列都定义成一个约束条件。

 

第1列定义成:(1,1)填了一个数字

第2列定义成:(1,2)填了一个数字

……

第9列定义成:(1,9)填了一个数字

第10列定义成:(2,1)填了一个数字

……

第18列定义成:(2,9)填了一个数字

……

第81列定义成:(9,9)填了一个数字

至此,用第1-81列完成了约束条件1:每个格子只能填一个数字

第N列(1≤N≤81)定义成:(X,Y)填了一个数字。

N、X、Y之间的关系是:X=INT((N-1)/9)+1;Y=((N-1) Mod 9)+1;N=(X-1)×9+Y

 

 

第82列定义成:在第1行填了数字1

第83列定义成:在第1行填了数字2

……

第90列定义成:在第1行填了数字9

第91列定义成:在第2行填了数字1

……

第99列定义成:在第2行填了数字9

……

第162列定义成:在第9行填了数字9

至此,用第82-162列(共81列)完成了约束条件2:每行1-9的这9个数字都得填一遍

第N列(82≤N≤162)定义成:在第X行填了数字Y。

N、X、Y之间的关系是:X=INT((N-81-1)/9)+1;Y=((N-81-1) Mod 9)+1;N=(X-1)×9+Y+81

 

 

第163列定义成:在第1列填了数字1

第164列定义成:在第1列填了数字2

……

第171列定义成:在第1列填了数字9

第172列定义成:在第2列填了数字1

……

第180列定义成:在第2列填了数字9

……

第243列定义成:在第9列填了数字9

至此,用第163-243列(共81列)完成了约束条件3:每列1-9的这9个数字都得填一遍

第N列(163≤N≤243)定义成:在第X列填了数字Y。

N、X、Y之间的关系是:X=INT((N-162-1)/9)+1;Y=((N-162-1) Mod 9)+1;N=(X-1)×9+Y+162

 

 

第244列定义成:在第1宫填了数字1

第245列定义成:在第1宫填了数字2

……

第252列定义成:在第1宫填了数字9

第253列定义成:在第2宫填了数字1

……

第261列定义成:在第2宫填了数字9

……

第324列定义成:在第9宫填了数字9

至此,用第244-324列(共81列)完成了约束条件4:每宫1-9的这9个数字都得填一遍

第N列(244≤N≤324)定义成:在第X宫填了数字Y。

N、X、Y之间的关系是:X=INT((N-243-1)/9)+1;Y=((N-243-1) Mod 9)+1;N=(X-1)×9+Y+243

 

至此,用了324列完成了数独的四个约束条件,矩阵的列定义完成

那接下来,就是把数独转换为矩阵

数独问题中,每个格子分两种情况。有数字的格子、没数字的格子。

 

有数字的格子

以例子来说明,在(4,2)中填的是7

把(4,2)中填的是7,解释成4个约束条件

1、在(4,2)中填了一个数字。

2、在第4行填了数字7

3、在第2列填了数字7

4、在第4宫填了数字7(坐标(X,Y)到宫N的公式为:N=INT((X-1)/3)×3+INT((Y-1)/3)+1)

 

那么这4个条件,分别转换成矩阵对应的列为

1、在(4,2)中填了一个数字。对应的列N=(4-1)×9+2=29

2、在第4行填了数字7。对应的列N=(4-1)×9+7+81=115

3、在第2列填了数字7。对应的列N=(2-1)×9+7+162=178

4、在第4宫填了数字7。对应的列N=(4-1)×9+7+243=277

 

于是,(4,2)中填的是7,转成矩阵的一行就是,第29、115、178、277列是1,其余列是0。把这1行插入到矩阵中去。

 

没数字的格子

还是举例说明,在(5,8)中没有数字

把(5,8)中没有数字转换成

(5,8)中填的是1,转成矩阵的一行就是,第44、118、226、289列是1,其余列是0。

(5,8)中填的是2,转成矩阵的一行就是,第44、119、227、290列是1,其余列是0。

(5,8)中填的是3,转成矩阵的一行就是,第44、120、228、291列是1,其余列是0。

(5,8)中填的是4,转成矩阵的一行就是,第44、121、229、292列是1,其余列是0。

(5,8)中填的是5,转成矩阵的一行就是,第44、122、230、293列是1,其余列是0。

(5,8)中填的是6,转成矩阵的一行就是,第44、123、231、294列是1,其余列是0。

(5,8)中填的是7,转成矩阵的一行就是,第44、124、232、295列是1,其余列是0。

(5,8)中填的是8,转成矩阵的一行就是,第44、125、233、296列是1,其余列是0。

(5,8)中填的是9,转成矩阵的一行就是,第44、126、234、297列是1,其余列是0。

把这9行插入到矩阵中。由于这9行的第44列都是1(不会有其他行的44列会是1),也就是说这9行中必只有1行(有且只有1行)选中(精确覆盖问题的定义,每列只能有1个1),是最后解的一部分。这就保证了最后解在(5,8)中只有1个数字。

这样,从数独的格子依次转换成行(1行或者9行)插入到矩阵中。完成了数独问题到精确覆盖问题的转换。同时在转换的过程中要把这个数的位置同时记住,用一个数组来保存。

然后有一个优化:在dancing(dep)函数调用的时候是直接调用_Head.Right来获得未求解列。由于精确覆盖问题是要求每个列都要覆盖到,因此,在算法中调用未求解列的先后顺序那就不是最重要了。假如,现在有两个未求解列C1和C2,C1列有8个元素,C2列有4个元素。最坏的情况,从C1列求解,需要调用8次Dance(K+1),而从C2列求解,需要调用4次Dance(K+1)。感觉上从C2列求解比从C1列求解效率要高些。因此,在Dance(K)函数调用的时候,先找寻列元素最少的未求解列,再依次求解,可能效率会高点。

代码:

  1 #include <cstdio>
  2 #include <fstream>
  3 #include <algorithm>
  4 #include <cmath>
  5 #include <deque>
  6 #include <vector>
  7 #include <queue>
  8 #include <string>
  9 #include <cstring>
 10 #include <map>
 11 #include <stack>
 12 #include <set>
 13 #include <sstream>
 14 #include <iostream>
 15 #define mod 1000000007
 16 #define eps 1e-6
 17 #define ll long long
 18 #define INF 0x3f3f3f3f
 19 using namespace std;
 20 
 21 const int ms=81*10;
 22 const int maxn=ms*4;
 23 int ans[maxn];
 24 struct DLX
 25 {
 26     int n,id;
 27     int L[maxn],R[maxn],U[maxn],D[maxn];
 28     int C[maxn],S[maxn],loc[maxn][3];//C代表列,S代表每列有的数字数量,loc代表这个数在数独中的位置和数值
 29     int H[ms];
 30     void init(int nn=0)
 31     {
 32         n=nn;
 33         for(int i=0;i<=n;i++) U[i]=D[i]=i,L[i]=i-1,R[i]=i+1;
 34         L[0]=n; R[n]=0;
 35         id=n;
 36         memset(S,0,sizeof(S));
 37         memset(H,-1,sizeof(H));
 38     }
 39     void Link(int x,int y,int px,int py,int k)
 40     {
 41         ++id;
 42         D[id]=y; U[id]=U[y];
 43         D[U[y]]=id; U[y]=id;
 44         loc[id][0]=px,loc[id][1]=py,loc[id][2]=k;//存放数的位置和数
 45         C[id]=y;
 46         S[y]++;//此列1的数量加一
 47         if(H[x]==-1) H[x]=L[id]=R[id]=id;
 48         else
 49         {
 50             int a=H[x];
 51             int b=R[a];
 52             L[id]=a; R[a]=id;
 53             R[id]=b; L[b]=id;
 54             H[x]=id;
 55         }
 56     }
 57     void Remove(int c)
 58     {
 59         L[R[c]]=L[c];
 60         R[L[c]]=R[c];
 61         for(int i=D[c];i!=c;i=D[i])
 62             for(int j=R[i];j!=i;j=R[j])
 63         {
 64             U[D[j]]=U[j];
 65             D[U[j]]=D[j];
 66             S[C[j]]--;
 67         }
 68     }
 69     void Resume(int c)
 70     {
 71         for(int i=U[c];i!=c;i=U[i])
 72             for(int j=R[i];j!=i;j=R[j])
 73         {
 74             S[C[j]]++;
 75             U[D[j]]=j;
 76             D[U[j]]=j;
 77         }
 78         L[R[c]]=c;
 79         R[L[c]]=c;
 80     }
 81     bool dfs(int step)
 82     {
 83         if(step==81) return true;
 84         if(R[0]==0) return false;
 85         int Min=INF,c=-1;
 86         for(int i=R[0];i;i=R[i])//优先循环1的数量少的一列
 87             if(Min>S[i]){ Min=S[i]; c=i; }
 88         Remove(c);
 89         for(int i=D[c];i!=c;i=D[i])
 90         {
 91             ans[step]=i;
 92             for(int j=R[i];j!=i;j=R[j]) Remove(C[j]);
 93             if(dfs(step+1)) return true;
 94             for(int j=L[i];j!=i;j=L[j]) Resume(C[j]);
 95         }
 96         Resume(c);
 97         return false;
 98     }
 99 }dlx;
100 int main()
101 {
102     char S[90];
103     while(scanf("%s",S)!=EOF)
104     {
105         if(S[0]=='e') break;
106         dlx.init(81*4);
107         int k=0,r=0;//r代表行
108         for(int x=0;x<9;x++)
109             for(int y=0;y<9;y++)
110             {
111                 char ch=S[k++];
112                 int a,b,c,d;//a表示约束一,b表示约束二,c表示约束三,d表示约束四
113                 if(ch=='.')
114                 {
115                     for(int i=1;i<=9;i++)
116                     {
117                         a=x*9+y+1;
118                         b=x*9+i+81;
119                         c=y*9+i+81+81;
120                         int s=(x/3)*3+y/3;
121                         d=s*9+i+81+81+81;
122                         ++r;
123                         dlx.Link(r,a,x,y,i);
124                         dlx.Link(r,b,x,y,i);
125                         dlx.Link(r,c,x,y,i);
126                         dlx.Link(r,d,x,y,i);
127                     }
128                 }
129                 else
130                 {
131                     int i=ch-'0';
132                     a=x*9+y+1;
133                     b=x*9+i+81;
134                     c=y*9+i+81+81;
135                     int s=(x/3)*3+y/3;
136                     d=s*9+i+81+81+81;
137                     ++r;
138                     dlx.Link(r,a,x,y,i);
139                     dlx.Link(r,b,x,y,i);
140                     dlx.Link(r,c,x,y,i);
141                     dlx.Link(r,d,x,y,i);
142                 }
143            }
144         dlx.dfs(0);
145         int res[10][10];
146         for(int i=0;i<81;i++)//将答案存放到一个数独数组中
147         {
148             int a=ans[i];
149             int x=dlx.loc[a][0],y=dlx.loc[a][1],k=dlx.loc[a][2];
150             res[x][y]=k;
151         }
152         for(int i=0;i<9;i++)
153             for(int j=0;j<9;j++) printf("%d",res[i][j]);
154         printf("\n");
155     }
156     return 0;
157 }

 

转载于:https://www.cnblogs.com/mzchuan/p/11434887.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值