1. 程式人生 > >Dancing Links ---- D

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個數;
  2. 每行的數字集合為[1, N^2],且不能重複;
  3. 每列的數字集合為[1, N^2],且不能重複;
  4. 每個“宮”的數字集合為[1, N^2],且不能重複(其中“宮”的意思就是N×N的格子。對於N=3的情況,就是“九宮格”); 現在問題是給定一個已經填了一些數字的數獨,求當N=3時的一種解,滿足以上四個限制條件。 在這裡插入圖片描述

轉變為精確覆蓋問題。行代表問題的所有情況,列代表問題的約束條件。每個格子能夠填的數字為[1,9],並且總共有9×9(即32×32)個格子,所以總的情況數為729種。也就是DancingLinks的行為729行。 列則分為四種:

  1. [0, 81)列 分別對應了81個格子是否被放置了數字。
  2. [82, 2*81)列 分別對應了9行,每行[1, 9]個數字的放置情況;
  3. [281, 381)列 分別對應了9列,每列[1, 9]個數字的放置情況;
  4. [381, 481)列 分別對應了9個“宮”,每“宮”[1, 9]個數字的放置情況; 所以總的列數為4*81=324列。如圖五-4-2所示。 在這裡插入圖片描述 舉個例子,對於在數獨棋盤的i行j列的格子(i, j)上放置一個數字k,那麼對應的Dancing Links的01矩陣行,一行上有四個“1”,分別對應四種約束條件:
    1. 格子限制: 行號*9 + 列號
    2. 行不重複限制: 81 + 行號 *9 + (k-1)
    3. 列不重複限制: 2*81 + 列號 *9 + (k-1)
    4. “宮”不重複限制: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;
}