From: http://blog.csdn.net/keyboardlabourer/article/details/13168391
1. 数独问题
在前一篇中简要地介绍了DLX算法,这一篇将主要讲DLX的应用——解决数独问题。DLX本身是用来解决exact cover问题,如果有一些问题能转化成exact cover问题,也就意味着也能用DLX求解。
数独(Sudoku)问题是在一个9 * 9的格子上,分为9个宫。有一些格子是已经填好了的,你的目标是把剩下的每一个格子填上1-9中的某一个数,使得每一行、每一列、每一个宫都包含1-9每个数各一次。比如:
. | 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 | . |
有些数独比较简单,则可以用纯粹的DFS(这篇有介绍)进行暴力求解。可是有些数独用DFS求解容易TLE,这时DLX就派上用场了。但是,困难在于如何构造一个矩阵A,使得数独问题转化成exact cover问题。我们可以如此构造矩阵A:
- 行:记录数独每一个格子所填的数。比如,1~9行依次记录数独第一个格子所填的数1~9,10~18行依次记录数独第二个格子所填的数1~9……
- 列:1~81列记录数独的81个格子是否已填,82~162列分别记录数独的1~9行有数字1~9,163~243列分别记录数独的1~9列有数字1~9,244~324列分别记录数独的1~9宫有数字1~9。
数独的格子是从左至右从上至下地编号的。为了便于后面的计算,一律将所有的编号从0开始,包括所填的数字。比如,数独的第二个格子的编号应为1,为数独的第0行第1列;如果要填数字2,则应是填1(也就是说0~8代表所填数字1~9)。如果将1格子填1,则相对应地要将A[10][1]、A[10][82]、A[10][172]、A[10][244]置为1。
由上述例子可以推导得出,数独的第r行第c列填k时,相应地将矩阵A中四个元素置为1:
A[9*(9*r+c)+k][9*r+c]=1
A[9*(9*r+c)+k][81+9*r+k]=1
A[9*(9*r+c)+k][162+9*c+k]=1
A[9*(9*r+c)+k][243+9*(3*(r/3)+c/3)+k]=1
从0格子开始,如果该格子在初始情况下已经填好,相应地将矩阵A中元素置为1;如果该格子为空,则依次填0~8,并相应地将矩阵A中元素置为1。如此数独问题转化成了exact cover问题:求矩阵A的行集合(共有81行)使得每列有且仅有一个1。在
矩阵A构造出来之后,只需将A改由双向十字链表存储,即可用DLX求解了。
2. 问题
2.1 POJ 3074
在将A改由双向十字链表存储时,用到了前一篇的init函数,并用到了G[ ]保存节点所在的行。注意到dfs函数中O[ ]记录的是搜索结果行中的一个元素,由于搜索结果共有81行,那么O[ ]应该开到81。在建双向十字链表时,是从1开始编号的。因此,G[O[ ]]-1才表示搜索结果在矩阵A中的所在行。矩阵A的每一行表示了对应的格子所填的数,G[O[ ]]表示了(G[O[ ]]-1)/9格子填(G[O[ ]]-1)%9。
源代码:
3074 | Accepted | 1140K | 79MS | C | 2673B | 2013-10-27 11:08:2 |
- #include "stdio.h"
- #include "string.h"
- #define MAXR 729
- #define MAXC 324
- #define MAXN 3241 //4*MAXR+MAXC+1
- int L[MAXN],R[MAXN],U[MAXN],D[MAXN],C[MAXN],G[MAXN],S[MAXC+1],O[81],H[MAXR+1];
- int A[MAXR][MAXC],ans[81];
- char sudoku[81];
- void fill(int r,int c,int k) //(r,c) of sudoku is filled with number k
- {
- int grid=9*r+c, row=9*(9*r+c)+k;
- A[row][grid]=1;
- A[row][81+9*r+k]=1;
- A[row][162+9*c+k]=1;
- A[row][243+9*(3*(r/3)+c/3)+k]=1;
- }
- void construct() //construct matrix A
- {
- int i,j,r,c;
- memset(A,0,sizeof(A));
- for(i=0;i<81;i++)
- {
- r=i/9; c=i%9;
- if(sudoku[i]=='.')
- {
- for(j=0;j<9;j++)
- fill(r,c,j);
- }
- else fill(r,c,sudoku[i]-'1');
- }
- }
- void build_cross_list() //build the cross list of matrix A
- {
- int i,j,count;
- for(i=1;i<=MAXC;i++)
- {
- L[i]=i-1; R[i]=i+1;
- U[i]=i; D[i]=i;
- C[i]=i;
- }
- L[0]=MAXC; R[0]=1;
- R[MAXC]=0;
- memset(H,-1,sizeof(H));
- memset(S,0,sizeof(S));
- count=MAXC+1;
- for(i=1;i<=MAXR;i++)
- for(j=1;j<=MAXC;j++)
- {
- if(!A[i-1][j-1]) continue;
- if(H[i]==-1)
- H[i]=L[count]=R[count]=count;
- else
- {
- L[count]=L[H[i]]; R[count]=H[i];
- R[L[H[i]]]=count; L[H[i]]=count;
- }
- U[count]=U[j]; D[count]=j;
- D[U[j]]=count; U[j]=count;
- C[count]=j; G[count]=i; //record the row of the node
- S[j]++;
- count++;
- }
- }
- void re_move(int c)
- {
- int i,j;
- L[R[c]]=L[c];
- R[L[c]]=R[c];
- for(i=D[c];i!=c;i=D[i])
- for(j=R[i];j!=i;j=R[j])
- {
- U[D[j]]=U[j];
- D[U[j]]=D[j];
- S[C[j]]--;
- }
- }
- void resume(int c)
- {
- int i,j;
- for(i=U[c];i!=c;i=U[i])
- for(j=L[i];j!=i;j=L[j])
- {
- S[C[j]]++;
- U[D[j]]=j;
- D[U[j]]=j;
- }
- L[R[c]]=c;
- R[L[c]]=c;
- }
- int dfs(int depth)
- {
- int i,j,c,min=10000;
- if(R[0]==0) //print the answer
- {
- for(i=0;i<depth;i++)
- ans[(G[O[i]]-1)/9]=(G[O[i]]-1)%9+1;
- for(i=0;i<81;i++)
- printf("%d",ans[i]);
- printf("\n");
- return 1;
- }
- for(i=R[0];i!=0;i=R[i])
- if(S[i]<min)
- {
- min=S[i];
- c=i;
- }
- re_move(c);
- for(i=D[c];i!=c;i=D[i])
- {
- O[depth]=i;
- for(j=R[i];j!=i;j=R[j])
- re_move(C[j]);
- if(dfs(depth+1)) return 1;
- for(j=L[i];j!=i;j=L[j])
- resume(C[j]);
- }
- resume(c);
- return 0;
- }
- int main()
- {
- while(scanf("%s",sudoku)&&sudoku[0]!='e')
- {
- construct();
- build_cross_list();
- dfs(0);
- }
- return 0;
- }
2.2 POJ 3076
16*16的数独,将3074中的一些数更改一下就可以了。
PE了一次,每个测试样例后需要空一行。
源代码:
3076 | Accepted | 16892K | 422MS | C | 3046B | 2013-10-27 16:28:41 |
- #include "stdio.h"
- #include "string.h"
- #define MAXR 4096
- #define MAXC 1024
- #define MAXN 17409 //4*MAXR+MAXC+1
- int L[MAXN],R[MAXN],U[MAXN],D[MAXN],C[MAXN],G[MAXN],S[MAXC+1],O[256],H[MAXR+1];
- int A[MAXR][MAXC];
- char sudoku[16][16],ans[256];
- void fill(int r,int c,int k) //(r,c) of sudoku is filled with number k
- {
- int grid=16*r+c, row=16*(16*r+c)+k;
- A[row][grid]=1;
- A[row][256+16*r+k]=1;
- A[row][512+16*c+k]=1;
- A[row][768+16*(4*(r/4)+c/4)+k]=1;
- }
- void construct() //construct matrix A
- {
- int i,j,k;
- memset(A,0,sizeof(A));
- for(i=1;i<16;i++)
- scanf("%s",sudoku[i]);
- for(i=0;i<16;i++)
- for(j=0;j<16;j++)
- {
- if(sudoku[i][j]=='-')
- {
- for(k=0;k<16;k++)
- fill(i,j,k);
- }
- else fill(i,j,sudoku[i][j]-'A');
- }
- }
- void build_cross_list() //build the cross list of matrix A
- {
- int i,j,count;
- for(i=1;i<=MAXC;i++)
- {
- L[i]=i-1; R[i]=i+1;
- U[i]=i; D[i]=i;
- C[i]=i;
- }
- L[0]=MAXC; R[0]=1;
- R[MAXC]=0;
- memset(H,-1,sizeof(H));
- memset(S,0,sizeof(S));
- count=MAXC+1;
- for(i=1;i<=MAXR;i++)
- for(j=1;j<=MAXC;j++)
- {
- if(!A[i-1][j-1]) continue;
- if(H[i]==-1)
- H[i]=L[count]=R[count]=count;
- else
- {
- L[count]=L[H[i]]; R[count]=H[i];
- R[L[H[i]]]=count; L[H[i]]=count;
- }
- U[count]=U[j]; D[count]=j;
- D[U[j]]=count; U[j]=count;
- C[count]=j; G[count]=i; //record the row of the node
- S[j]++;
- count++;
- }
- }
- void re_move(int c)
- {
- int i,j;
- L[R[c]]=L[c];
- R[L[c]]=R[c];
- for(i=D[c];i!=c;i=D[i])
- for(j=R[i];j!=i;j=R[j])
- {
- U[D[j]]=U[j];
- D[U[j]]=D[j];
- S[C[j]]--;
- }
- }
- void resume(int c)
- {
- int i,j;
- for(i=U[c];i!=c;i=U[i])
- for(j=L[i];j!=i;j=L[j])
- {
- S[C[j]]++;
- U[D[j]]=j;
- D[U[j]]=j;
- }
- L[R[c]]=c;
- R[L[c]]=c;
- }
- int dfs(int depth)
- {
- int i,j,c,min=10000;
- if(R[0]==0) //print the answer
- {
- for(i=0;i<depth;i++)
- ans[(G[O[i]]-1)/16]=(G[O[i]]-1)%16+'A';
- for(i=0;i<16;i++)
- {
- for(j=0;j<16;j++)
- printf("%c",ans[16*i+j]);
- printf("\n");
- }
- printf("\n");
- return 1;
- }
- for(i=R[0];i!=0;i=R[i])
- if(S[i]<min)
- {
- min=S[i];
- c=i;
- }
- re_move(c);
- for(i=D[c];i!=c;i=D[i])
- {
- O[depth]=i;
- for(j=R[i];j!=i;j=R[j])
- re_move(C[j]);
- if(dfs(depth+1)) return 1;
- for(j=L[i];j!=i;j=L[j])
- resume(C[j]);
- }
- resume(c);
- return 0;
- }
- int main()
- {
- while(scanf("%s",sudoku[0])!=EOF)
- {
- construct();
- build_cross_list();
- dfs(0);
- }
- return 0;
- }