[POJ2676]Sudoku

原题入口

这个题 题意很明显 就是个求数独的题目。

  1. 常规做法:暴力枚举+判断可行性(会TLE)
  2. 进阶做法:一边算一边用数组标记(400多ms)
  3. 超神做法:用DLX算法(基本0ms)

这三种做法我都试过 一步步优化程度提升 最后DLX特别快 基本算普通的不需要时间 算骨灰级难度的也很快

所以我在这里介绍的就是DLX算法了  一开始不太明白 看了个大佬的程序 就基本上懂了 十分简洁明了 博客链接

但那个大佬的代码没啥注释  我就将她的代码进行略微改编,然后增加注释

我还是简单介绍下DLX算法(来自《算法竞赛入门经典-训练指南》)

这个算法是用于解决一些精准覆盖的问题 即有几个集合中包含一些元素 选取一些集合 使每个元素不重复且恰好出现一次。(可以运用到,N皇后,数独即一些特殊的问题上面去)

需要用一个01行列的表构造(以后有时间补上书上的说明)

很容易想得出是用搜索写法。 这里就是运用的搜索中的X算法: 就是选取还没有选过的一个元素,从包含它的元素集合中挑选,然后删除其他所有包含这个元素的集合。最后回溯时又复原这个集合。

为了更快的删除和恢复。所以我们就需要一个特殊的数据结构来维护,这里就出现了神奇的Dancing-Links(舞蹈链)。

这个数据结构记录了它上下左右最近的一个为1的存在节点,这个是运用的循环链表实现的,能够达到很快遍历该行和该列的目的。而且恢复也十分容易,只要修改相邻的节点就行了。 由于是循环列表,所以最后一列右边的节点就是第一列的节点,其他三个方面类推。

这个数独是怎么运用的后面去补吧。。。 最重要的是要分清本身的棋盘和舞蹈链组成的舞池的区别(因为都是矩阵 一开始不好区分)

 

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
#define Set(a, v) memset(a, v, sizeof(a))
#define For(i, l, r) for(int i = (l); i <= (int)(r); ++i)
#define Fordown(i, r, l) for(int i = (r); i >= (int)(l); --i)
#define Travel(i, A, s) for(int i = A[s]; i != s; i = A[i])
//之所以可以这样遍历 因为 这是个循环队列 可以遍历一列或者一行 
using namespace std;

inline int read(){
    int x = 0, fh = 1; char ch;
    for(; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for(; isdigit(ch); ch = getchar()) x = (x<<1) + (x<<3) + (ch^'0');
    return x * fh;
}

const int maxr = 9*9*9 + 10;
const int maxc = 9*9*4 + 10;
const int inf = 0x3f3f3f3f;
int L[maxc+maxr*5], R[maxc+maxr*5], U[maxc+maxr*5], D[maxc+maxr*5]; //maxc+maxr*5是总共的最大节点数
//这边分别是四个指针,left,right,up,down上下左右 分别指向别的节点位置 
int S[maxc]; //统计每列1的节点个数
int nRow[maxc+maxr*5], nCol[maxc+maxr*5]; //储存每个节点的行和列
int Head[10][10][10]; //储存每个位置 (数独中) 每个数字所对应的节点 
int cnt = 0; //存储共有多少个节点
int G[10][10]; //存储答案图(和原来的图) 

inline int sub_grid (int x, int y) {return ((x-1)/3)*3+((y-1)/3+1);} //把每个原数独中的节点所在宫的编号求出来 

void insert (int c, int cnt) {
    U[D[c]] = cnt; 
    D[cnt] = D[c];  
    U[cnt] = c; 
    D[c] = cnt;  
    ++S[c];
    nCol[cnt] = c;
} //插入一个节点 

void remove (int c) {
    L[R[c]] = L[c]; //右边的左边 变为这个节点的左边 
    R[L[c]] = R[c]; //右边的左边 变为这个节点的左边
    //即删除这一列 
    Travel (i, D, c) //向下遍历 
        Travel (j, R, i) { //向右遍历 
            U[D[j]] = U[j]; //楼下的楼上变为楼上  
            D[U[j]] = D[j]; //楼下的楼上变为楼上 
            --S[nCol[j]]; //减少遍历行中含有1的列的所统计个数 
        } //删除包含这列有1的行 
} //删除一列 

void resume (int c) {
    Travel (i, U, c)
        Travel (j, L, i) {
            U[D[j]] = D[U[j]] = j;
            ++S[nCol[j]];
        }
    L[R[c]] = R[L[c]] = c;
} //恢复一列 

bool dfs (int d) {
    if (d > 81) return true;
    int c, minn = inf;
    Travel (i, R, 0) {
        if (!S[i]) return false;
        if (S[i] < minn) {
            minn = S[i];
            c = i;
        }
    }
    remove(c);
    Travel(i, D, c) {
        int tmp = nRow[i];
        G[tmp/100][(tmp/10)%10] = tmp % 10;
        Travel (j, R, i)
            remove (nCol[j]);
        if (dfs(d+1)) return true;
        Travel (j, L, i)
            resume(nCol[j]);
    }
    resume(c);
    return false;
}


void init() {
    For (i, 0, 81*4) {
        S[i] = 0; //把每列的个数清零 
        U[i] = D[i] = i; //这个节点上下变为自己 
        L[i] = i-1; R[i] = i+1; 
        nCol[i] = 0; //
    }
    R[81*4] = 0; L[0] = 81*4;
    cnt = 81*4; 
    For (i, 1, 9)
        For (j, 1, 9) {
            if (G[i][j]) { //有数字的情况 
                int k = G[i][j]; //取出这一位 
                For (u, 1, 4) {
                    L[cnt+u] = cnt + u - 1; //
                    R[cnt+u] = cnt + u + 1; //
                    nRow[cnt+u] = 100*i + 10*j + k; //把这行标号 
                }
                L[cnt+1] = cnt+4; R[cnt+4] = cnt+1;
                Head[i][j][k] = cnt + 1;
                insert ((i-1) * 9 + j, cnt+1);
                insert (81 + (i-1) * 9 + k, cnt+2);
                insert (81*2 + (j-1) * 9 + k, cnt+3);
                insert (81*3 + (sub_grid(i, j) - 1) * 9 + k, cnt+4);
                cnt += 4;
            }
            else {
                For (k, 1, 9) {
                For (u, 1, 4) {
                    L[cnt+u] = cnt + u - 1;
                    R[cnt+u] = cnt + u + 1;
                    nRow[cnt+u] = 100*i + 10*j + k;
                    }
                L[cnt+1] = cnt+4; R[cnt+4] = cnt+1;
                Head[i][j][k] = cnt + 1;
                insert ((i-1) * 9 + j, cnt+1);
                insert (81 + (i-1) * 9 + k, cnt+2);
                insert (81*2 + (j-1) * 9 + k, cnt+3);
                insert (81*3 + (sub_grid(i, j) - 1) * 9 + k, cnt+4);
                cnt += 4;
                }
            }
        }
}

void solve () {
    int k = 0;
    For (i, 1, 9)
        For (j, 1, 9)
        if (G[i][j]) {
            ++k; int v = Head[i][j][G[i][j]];
            remove (nCol[v] );
            Travel(u, R, v) 
                remove (nCol[u]);
        }
    dfs(k+1);
    For (i, 1, 9) {
        For (j, 1, 9)
            putchar (G[i][j] + '0');
        putchar ('\n');
    }
 //   putchar ('\n');
}

void input() {
        Set(G, 0);
        int cnt_x = 1, cnt_y = 0;
        while (cnt_x != 9 || cnt_y != 9) {
            char ch = getchar();
            if (!isdigit(ch)) continue; //判断是否为数字 (特殊的读入方式) 
            ++cnt_y;
            if (cnt_y == 10) {
                cnt_y = 1;
                ++cnt_x; //到这行结束 跳到下一行 
            }
            G[cnt_x][cnt_y] = (ch ^ '0'); //把当前这个数独的位置记下来
        }
}

int main (){
    int t = read();
    while (t--) {
    input(); //输入 
    init(); //预处理 
    solve(); //解决问题 
    }
}

 

posted @ 2017-07-20 21:10  zjp_shadow  阅读(360)  评论(0编辑  收藏  举报