您的位置:首页 > 其它

POJ 3076 Sudoku

2015-10-05 22:36 239 查看
Sudoku

Time Limit: 10000MS Memory Limit: 65536K
Total Submissions: 4628 Accepted: 2237
Description
A Sudoku grid is a 16x16 grid of cells grouped in sixteen 4x4 squares, where some cells are filled with letters from A to P (the first 16 capital letters of the English alphabet), as shown in figure 1a. The game is to fill all
the empty grid cells with letters from A to P such that each letter from the grid occurs once only in the line, the column, and the 4x4 square it occupies. The initial content of the grid satisfies the constraints mentioned above and guarantees a unique solution.



Write a Sudoku playing program that reads data sets from a text file.
Input
Each data set encodes a grid and contains 16 strings on 16 consecutive lines as shown in figure 2. The i-th string stands for the i-th line of the grid, is 16 characters long, and starts from the first position of the line. String
characters are from the set {A,B,…,P,-}, where – (minus) designates empty grid cells. The data sets are separated by single empty lines and terminate with an end of file.
Output
The program prints the solution of the input encoded grids in the same format and order as used for input.
Sample Input
--A----C-----O-I
-J--A-B-P-CGF-H-
--D--F-I-E----P-
-G-EL-H----M-J--
----E----C--G---
-I--K-GA-B---E-J
D-GP--J-F----A--
-E---C-B--DP--O-
E--F-M--D--L-K-A
-C--------O-I-L-
H-P-C--F-A--B---
---G-OD---J----H
K---J----H-A-P-L
--B--P--E--K--A-
-H--B--K--FI-C--
--F---C--D--H-N-

Sample Output
FPAHMJECNLBDKOGI
OJMIANBDPKCGFLHE
LNDKGFOIJEAHMBPC
BGCELKHPOFIMAJDN
MFHBELPOACKJGNID
CILNKDGAHBMOPEFJ
DOGPIHJMFNLECAKB
JEKAFCNBGIDPLHOM
EBOFPMIJDGHLNKCA
NCJDHBAEKMOFIGLP
HMPLCGKFIAENBDJO
AKIGNODLBPJCEFMH
KDEMJIFNCHGAOPBL
GLBCDPMHEONKJIAF
PHNOBALKMJFIDCEG
IAFJOECGLDPBHMNK


这个数独的题目用到的是之前一篇文章中用到的DLX算法,即将数独问题转化为精确覆盖问题。具体转化的方法是根据数独的特性来的,首先作为数独,它的约束条件有4个

1.每一行每个数字只能填一遍

2.每一列每个数字只能填一遍

3.每个宫每个数字只能填一遍

4.每个格子只能填一个数字。

根据上面的规则,我们将数独问题转化为精确覆盖问题。矩阵构造如下:

第1列定义为第一行填了数字1

第2列定义为第一行填了数字2

第16列定义为第一行填了数字16

第17列定义为第二行填了数字1

以此类推,第256列定义为第16行填了数字16

这样,用1-256列完成了第一个约束条件,下面我们继续:

第257列定义为第1列填了数字1

第258列定义为第1列填了数字2

第512列定义为第16列填了数字16

这样,用257-512列完成了第二个约束条件

第513列定义为在第一宫填了数字1

第514列定义了在第一宫填了数字2

第768列定义了在第十六宫填了数字16

这样,513-768列完成了第三个约束条件

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

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

在1024列定义了(16,16)填了一个数字

这样,769-1024列完成了第四个约束条件

举个例子:

例如(6,4)这个点填了3,分别转化为矩阵中对应的列为:

1.在第6行中填了数字3,对应列数为:(6 - 1) * 16 + 3

2.在第4列中填了数字3,对应列数为:16 * 16  + (4 - 1) + 3

3.在第5宫中填了数字3,对应列数为:16 * 16 * 2 + (5 - 1) * 16 + 3 (其中宫的算法是:palace = (i - 1) /4 * 4 + (j - 1) / 4 + 1)

4.在(6,4)填了一个数字,对应列数为:16 * 16 * 3 + 84  (其中位置的算法是:pos = (i - 1) * 16 + j)

这样,对应(6,4)这个填3对应矩阵的一行就是:上述的列数置为1,其余列都是0,将这一列加入到矩阵中去。

至此所有约束条件完成了,矩阵构造完成了,实际上矩阵已然转化为精确覆盖问题,就可以使用之前转载的一篇博客中的DLX算法求解:http://blog.csdn.net/chilumanxi/article/details/48297149,具体见代码:

/*************************************************************************
> File Name: Sudoku.cpp
> Author: Zhanghaoran0
> Mail: chiluamnxi@gmail.com
> Created Time: 2015年09月08日 星期二 20时52分43秒
************************************************************************/

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 5000;
const int M = 700000;

int U[M], D[M], L[M], R[M];
int C[M];
int H
, S
;
int mark[M];
int size, n, m, Suc
, flag;
int tnum[M];

void Link(int a, int b){
S[b] ++; //对应列数结点数+1
C[size] = b; //记录列数
U[size] = U[b]; //将上下的联系连接上
D[U[b]] = size;
D[size] = b;
U[b] = size;
if(H[a] == -1) //如果该行还没有加入过结点,指向自己
H[a] = L[size] = R[size] = size;
else{
L[size] = L[H[a]]; //若已经加入过结点,则将此结点加入,将左右的连接关系加上。
R[L[H[a]]] = size;
R[size] = H[a];
L[H[a]] = size;
}
mark[size] = a;
size ++;
}

void Delete(int a){ //删除结点
L[R[a]] = L[a];
R[L[a]] = R[a];
for(int i = D[a]; i != a; i = D[i]){
for(int j = R[i]; j != i; j = R[j]){
U[D[j]] = U[j];
D[U[j]] = D[j];
S[C[j]] --;
}
}
}

void Resume(int a){
for(int i = U[a]; i != a; i = U[i]){ //只是改变了周围四个指针的数值,所以很简单就能恢复
for(int j = L[i]; j != i; j = L[j]){
U[D[j]] = j;
D[U[j]] = j;
S[C[j]] ++;
}
}
L[R[a]] = a;
R[L[a]] = a;
}

void Dance(int a){
int Min = N;
int t;
if(!R[0]){ //如果作为辅助的第一行没有任何结点的话就视为完成
flag = 1;
sort(Suc, Suc + a);
for(int i = 0; i < a; i ++){
cout << (char)(tnum[mark[Suc[i]]] + 'A' - 1);
if((i + 1) % 16 == 0)
cout << endl;
}
cout << endl;
return ;
}

for(int i = R[0]; i; i = R[i]){ //优先删除结点少的列数
if(S[i] < Min){
Min = S[i];
t = i;
}
}
Delete(t);
for(int i = D[t]; i != t; i = D[i]){
Suc[a] = i; //用这个数组来记录一行所有的字母
for(int j = R[i]; j != i; j = R[j])
Delete(C[j]);
Dance(a + 1);
if(flag)
return ;
for(int j = L[i]; j != i; j = L[j])
Resume(C[j]);
}
Resume(t); //回溯
}

char Map[20][20];
int Matrix[16 * 16 * 16 + 10][16 * 16 * 4 + 10];
int main(void){
while(scanf("%s", Map[0]) == 1){
for(int i = 1; i < 16; i ++)
scanf("%s", Map[i]);
memset(Matrix, 0, sizeof(Matrix));
m = 16 * 16 * 4;
n = 0;
for(int i = 1; i <= 16; i ++){
for(int j = 1; j <= 16; j ++){
int row = i;
int col = j;
int palace = (i - 1) / 4 * 4 + (j - 1) / 4 + 1; //宫的位置
int pos = (i - 1) * 16 + j; //实际位置
if(Map[i - 1][j - 1] == '-'){
for(int k = 1; k <= 16; k ++){ //对于还没有存入数字的坐标,将所有情况写入
++ n;
tnum
= k; //记录存入数组的个数和对应的数字
Matrix
[(row - 1) * 16 + k] = 1; //以下四个条件分别对应行,列,宫,实际位置的条件
Matrix
[16 * 16 + (col - 1) * 16 + k] = 1;
Matrix
[16 * 16 * 2 + (palace - 1) * 16 + k] = 1;
Matrix
[16 * 16 * 3 + pos] = 1;
}
}
else{
int k = Map[i - 1][j - 1] - 'A' + 1; //对于实际存在的数字,同上写入
++ n;
tnum
= k;
Matrix
[(row - 1) * 16 + k] = 1;
Matrix
[16 * 16 + (col - 1) * 16 + k] = 1;
Matrix
[16 * 16 * 2 + (palace - 1) * 16 + k] = 1;
Matrix
[16 * 16 * 3 + pos] = 1;
}
}
}

for(int i = 0; i <= m; i ++){ //初始化所有的结点的连接关系,上下的关系没有连接所以先指向自己
S[i] = 0;
D[i] = U[i] = i;
L[i + 1] = i;
R[i] = i + 1;

}
R[m] = 0; //循环的性质
size = m + 1;
memset(H, - 1, sizeof(H));
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
if(Matrix[i][j])
Link(i, j); //将所有已经存在的点连接
}
}
flag = 0;
Dance(0);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj DLX