zoukankan      html  css  js  c++  java
  • [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 ('
    ');
        }
     //   putchar ('
    ');
    }
    
    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(); //解决问题 
        }
    }
  • 相关阅读:
    【树形DP】ZJOI2008 骑士
    【博弈论】CF 1215D Ticket Game
    【状态压缩DP】HDU 4352 XHXJ'S LIS
    【纯水题】CF 833A The Meaningless Game
    【不知道怎么分类】NOIP2016 蚯蚓
    【状态压缩DP】SCOI2009 围豆豆
    操作系统总结
    概率问题总结
    C++虚函数原理
    一些baidu面经
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/7214505.html
Copyright © 2011-2022 走看看