POJ 3074 Sudoku

Sudoku

Time Limit: 1000MS Memory Limit: 65536K

Description

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

题意:

给出数独,让你补充完整。

思路:

做过poj2676的都知道用dfs加上剪枝可以解决数独的题目,但是有些时候空白太多的话复杂度会有点大,这题如果是想用dfs+剪枝的话会超时,因为有些样例空白太多了,所以这里用一个叫做dancing links的算法,算法的本质是双向链表。
首先,我这段代码有很多的注释结构体数组的注释对应着数组的作用,其他的是一步步研究算法+代码的时候对于很多不懂的地方,做出的输出,以便于理解,所以我觉得还是留着比较好,现在说说思路:
① 我们要明白什么是双向链表,就是一个链表前后相连或者左右相连,和顺序链表不同的是,这个链表可以从左向右或者从右向左,而双向链表是舞蹈链。
② 首先来看主函数,其他理解应该不大困难,最难的应该是r, c1, c2, c3, c4的理解了,那个形成这些变量的自定义函数中,我们可以发现都是x * N * N + y * N + Z类似这样的,这样的话就是防止重复用的,r代表着某一个点的某一个数字的编号所以最多有9 * 9 * 9 = 729种,假如要满足数独的话,总共有4个条件:

  • 1 每一个点都要有数字,对应c1(0, i, j)
  • 2 每一行的数字都只能有一个,对应c2(1,i,k)
  • 3 每一列的数字都只能有一个,对应c3(2,j,k)
  • 4 每一个九宫格的数字都只能有一个,对应c4(3,i / 3 * 3 + j / 3,k)

c1的编号是从1-81的,c2是从82-162,c3是163-243,c4是244-324。
③ 然后看看链接的这个函数,函数中我们可以看出来使用双向链表相连,和c有关系的是将链表上下相连,所以也就是说将c相同连在了一起,而c相同的情况就是i相同k相同,这就说明了在这一行某个数字出现了多次,所以将其连在一起。左右相连的就是r相同的时候,其实在每一个循环里面r都是不同的,因为r对应的i,j,k三个点,但是每一个循环里面r出现了4次,实际上就是将c1, c2, c3, c4通过r左右相连。
④之后就是最关键的dance了!我们首先去循环一下看看列最少的进行删除操作,为什么要列最少,因为可选择数字的少,假如只有一个可选择的数字,那么这个数字就可以直接确定下来了,然后把这个点的相关的东西全部删除,之后开始删除元素所在列的编号,再往下不断递归,知道元素0的右边没有元素了为止,那就是正确答案了,最后输出的那些东西把其运算一下就知道都代表的是什么了。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 9;
const int maxn = N * N * N + 10;
const int maxm = N * N * 4 + 10;
const int maxnode = maxn * 4 + maxm + 10;
char g[maxn];
struct DLX {
    int n, m, size;
    int u[maxnode]; //编号为x的上方第一个元素的编号
    int d[maxnode]; //编号为x的下方第一个元素的编号
    int l[maxnode]; //编号为x的左方第一个元素的编号
    int r[maxnode]; //编号为x的右方第一个元素的编号
    int row[maxnode]; //编号为x的元素所在行的编号
    int col[maxnode]; //编号为x的元素所在列的编号
    int h[maxn]; //第x行的头元素编号
    int s[maxm]; //第x列的元素个数
    int ans[maxn];
    void init(int n, int m) {
        n = n;
        m = m;
        for (int i = 0; i <= m; i++) {
            s[i] = 0;
            u[i] = d[i] = i;
            l[i] = i - 1;
            r[i] = i + 1;
        }
        r[m] = 0;
        l[0] = m;
        size = m;
        memset(h, -1, sizeof(h));
    }
    void link(int ro, int c) {
        //cout << c << " " << ro << endl;
        //if (c == 84) cout << ro << endl;
        size++;
        s[c]++;
        col[size] = c;
        row[size] = ro;
        d[size] = d[c];
        u[d[c]] = size;
        u[size] = c;
        d[c] = size;
        //cout << c << " " << d[c] << " " << size << " " << d[size] << endl;
        //for (int i = d[c]; i != c; i = d[i]) cout << i << " ";
        //cout << endl;
        if (h[ro] < 0) h[ro] = l[size] = r[size] = size;
        else {
            r[size] = r[h[ro]];
            l[r[h[ro]]] = size;
            l[size] =  h[ro];
            r[h[ro]] = size;
        }
        //cout << h[ro] << " " << r[h[ro]] << " " << r[r[h[ro]]] << " " << r[ro] << " " << ro << endl;
    }
    void removen(int c) {
        l[r[c]] = l[c];
        r[l[c]] = r[c];
        //cout << c << endl;
        for (int i = d[c]; i != c; i = d[i]) {
            for (int j = r[i]; j != i; j = r[j]) {
                //cout << i << " " << j << endl;
                //cout << col[j] << endl;
                u[d[j]] = u[j];
                d[u[j]] = d[j];
                s[col[j]]--;
            }
        }
    }
    void resumen(int 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 step) {
        //cout << r[0] << endl;
        if (r[0] == 0) {
            for (int i = 0; i < step; i++) g[(ans[i] - 1) / N] = (ans[i] - 1) % N + '1';
            for (int i = 0; i < N * N; i++) printf("%c", g[i]);
            printf("\n");
            return true;
        }
        int c = r[0];
        for (int i = r[0]; i != 0; i = r[i]) {
            //cout << c << " " << s[c] << " " << i << " " << s[i] << endl;
            if (s[i] < s[c]) c = i;
        }
        removen(c);
        for (int i = d[c]; i != c; i = d[i]) {
            ans[step] = row[i];
            //cout << step << " " << row[i] << endl;
            //cout << row[i] << endl;
            for (int j = r[i]; j != i; j = r[j]) removen(col[j]);
            if (dance(step + 1)) return true;
            for (int j = l[i]; j != i; j = l[j]) resumen(col[j]);
        }
        resumen(c);
        return false;
        /*int cnt = 82 + 81;
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= 9; j++) {
                cout << s[cnt++] << " ";
            }
            cout << endl;
        }*/
    }
};
DLX dlx;
void getnum(int &r, int &c1, int &c2, int &c3, int &c4, int i, int j, int k) {
    r = i * N * N + j * N + k;
    c1 = 0 * N * N + i * N + j + 1;
    c2 = 1 * N * N + i * N + k;
    c3 = 2 * N * N + j * N + k;
    c4 = 3 * N * N + (i / 3 * 3 + j / 3) * N + k;
}
int main() {
    while (scanf("%s", g) != EOF && g[0] != 'e') {
        dlx.init(N * N * N, N * N * 4);
        int cnt = 0;
        /*for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                cout << g[cnt++] << " ";
            }
            cout << endl;
        }*/
        int r, c1, c2, c3, c4;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                for (int k = 1; k <= N; k++) {
                    if (g[i * N + j] == '.' || g[i * N + j] == k + '0') {
                        getnum(r, c1, c2, c3, c4, i, j, k);
                        //cout << r << " " << c1 << " " << c2 << " " << c3 << " " << c4 << " " << " " << i << " " << j << " " << k << endl;
                        dlx.link(r, c1);
                        dlx.link(r, c2);
                        dlx.link(r, c3);
                        dlx.link(r, c4);
                    }
                }
            }
        }
        dlx.dance(0);
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值