• POJ2676 (数独问题 + DLX + 状态优化顺序)


    (1)最简单的最是去暴力DFS搜索答案 , 很容易想到 , 每行每列的方式去搜索 , 不过效率是真的不行;但这个还是给出代码 ,毕竟打了也不容易呀!

    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int maxn=10;
    int map[maxn][maxn];
    bool row[maxn][maxn];
    bool col[maxn][maxn];
    bool grid[maxn][maxn];
    bool dfs(int r,int c)
    {
        if(r==9) return true;//构造完毕
        bool flag=false;
        if(map[r][c])
        {
            if(c==8) flag=dfs(r+1,0);
            else flag=dfs(r,c+1);
            return flag;
        }
        int k=(r/3)*3+c/3;
        for(int i=1;i<=9;i++)if(!row[r][i]&&!col[c][i]&&!grid[k][i])
        {
            row[r][i]=col[c][i]=grid[k][i]=true;
            map[r][c]=i;
            if(c==8) flag=dfs(r+1,0);
            else flag=dfs(r,c+1);
            if(flag) return true;
            map[r][c]=0;
            row[r][i]=col[c][i]=grid[k][i]=false;
        }
        return false;
    }
    int main()
    {
        int T; scanf("%d",&T);
        while(T--)
        {
            memset(row,0,sizeof(row));
            memset(col,0,sizeof(col));
            memset(grid,0,sizeof(grid));
            for(int i=0;i<9;i++)
            for(int j=0;j<9;j++)
            {
                char x;
                scanf(" %c",&x);
                map[i][j]= x-'0';
                if(map[i][j])
                {
                    row[i][map[i][j]]=true;
                    col[j][map[i][j]]=true;
                    int k=(i/3)*3+j/3;
                    grid[k][map[i][j]]=true;
                }
            }
            dfs(0,0);
            for(int i=0;i<9;i++)
            {
                for(int j=0;j<9;j++)
                    printf("%d",map[i][j]);
                printf("
    ");
            }
        }
        return 0;
    }
    View Code

    (2)还有个更快的 ,就是利用舞蹈链(DLX)这种数据结构去快速实现这种问题 。(DLX:这里先给出模板,不先学习)

    //******************************************************//
    //输入T表示T组数据。                                    //
    //每组数据为9个长度为9的字符串,空白处以字符0替代。        //
    //POJ2676                                               //
    //******************************************************//
    
    #include <bits/stdc++.h>
    using namespace std;
    const int maxnode = 100010;
    const int MaxM = 1010;
    const int MaxN = 1010;
    struct DLX{
        int n, m, size;                 //行数,列数,总数
        int U[maxnode], D[maxnode], R[maxnode], L[maxnode], Row[maxnode], Col[maxnode];
        int H[MaxN], S[MaxM];           //S记录该列剩余1的个数,H表示该行最左端的1
        int ansd, 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 r, int c){
            size++;
            Col[size] = c, Row[size] = r;
            S[c]++;
            U[size] = U[c], D[size] = c;
            D[U[c]] = size;
            U[c] = size;
            if (H[r] != -1) {
                R[size] = H[r] ;
                L[size] = L[H[r]] ;
                R[L[size]] = size ;
                L[R[size]] = size ;
            }
            else
                H[r] = L[size] = R[size] = size ;
        }
        void remove(int c){//覆盖第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[Col[j]];
                }
        }
        void resume(int c){
            for(int i = U[c]; i != c; i = U[i])
                for(int j = L[i]; j != i; j = L[j]){
                    ++ S[Col[j]];
                    U[D[j]] = j;
                    D[U[j]] = j;
                }
            L[R[c]] = R[L[c]] = c;
        }
        //d为递归深度
        bool dance(int d){
            if(R[0] == 0){
                ansd = d;
                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]){
                ans[d] = Row[i];
                for(int j = R[i]; j != i; j = R[j]) remove(Col[j]);
                if(dance(d+1)) return true;
                for(int j = L[i]; j != i; j = L[j]) resume(Col[j]);
            }
            resume(c);
            return false;
        }
    };
    DLX g;
    char s[15][15];
    int main(){
        int t;
        scanf("%d", &t);
        while(t--){
            for(int i = 0; i < 9; i++)
                scanf("%s", s[i]);
    
            g.init(81*9, 81+81+81+81);
    
            for(int i = 0; i < 9; i++)
                for(int j = 0; j < 9; j++){
                    int x = i, y = j, z = x/3*3+y/3, w = i*9+j;
                    if(s[i][j] == '0'){
                        for(int k = 1; k <= 9; k++){
                            g.Link(w*9+k, w+1);
                            g.Link(w*9+k, 81+x*9+k);
                            g.Link(w*9+k, 162+y*9+k);
                            g.Link(w*9+k, 243+z*9+k);
                        }
                    }
                    else {
                        int t = s[i][j]-'0';
                        g.Link(w*9+t, w+1);
                        g.Link(w*9+t, 81+x*9+t);
                        g.Link(w*9+t, 162+y*9+t);
                        g.Link(w*9+t, 243+z*9+t);
                    }
                }
            g.dance(0);
    
            for(int i = 0; i < g.ansd; i++){
                int t = g.ans[i];
                int a = (t-1)/9, b = (t-1)%9+'1';
                s[a/9][a%9] = b;
            }
            for(int i = 0; i < 9; i++)
                puts(s[i]);
        }
        return 0;
    }
    View Code

    (3) 还有就是重要需要讲解的状态搜索:

    1、答案

    状态:我们关心数独每个位置填了什么数。我们需要在每个状态中找出没有填的位置,检查有哪些值可以填。这些可以填的值构成了向下递归的分支。(状态就是每个每个时刻的9*9数独)
    搜索边界:1、所有位置填完了,找到答案。2、发现某个位置没有能填的值,搜索失败。
    提醒:对于每个状态下,我们要找的是1个位置填什么,而不是枚举所有位置和能填的数(其他位置会在更深的地方被搜索到)。
    2、剪枝

    优化搜索顺序:如果是人类玩数独,策略一定是先填上“已经能够唯一确定的位置”,考虑在该位置填什么数,再向下搜索。所以我们在每个状态下选择所有未填的位置中,能填合法数字最少的位置,考虑在这上面填数。
    3、位运算

    对于每行,每列,每个九宫格,分别用一个9为二进制数保存哪些数字还可以填。
    对于每个位置,把他锁在的行,列,九宫格对应的数取 & 运算就可以得到剩余哪些数可以填。lowbit(x)取出能填的数。
    当某个位置的数填上时把对应的行,列,九宫格对应位置改为0。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    char str[10][10];
    int row[10], col[10], grid[9], cnt[512], num[512], tot;
     int g(int x, int y){
        return ((x / 3) * 3) + (y / 3);//在第几个九宫格
    }
     void flip(int x, int y, int z){
        row[x] ^= 1<<z; //x行z不能填了
        col[y] ^= 1<<z;
        grid[g(x,y)] ^= 1<<z;
    }
    
    bool dfs(int now){//剩余要填的数的总数
        if(now == 0)return 1;
        //优化顺序:枚举每个位置,找能填的最少的填
        int t = 10, x, y;
        for(int i = 0; i < 9; i++){
            for(int j = 0; j < 9; j++){
                if(str[i][j] != '0')continue;
                int val = row[i] & col[j] & grid[g(i,j)];
                if(!val)return 0;
                if(cnt[val] < t){///查看那个小宫格填入好
                    t = cnt[val];
                    x = i, y = j;
                }
            }
        }
        //填数
        int val = row[x] & col[y] & grid[g(x,y)];
        for(; val; val -= val&-val){
            int z = num[val&-val];//能填的数
            str[x][y] = '1'+z;
            flip(x,y,z);
            if(dfs(now-1))return 1;
            flip(x,y,z);
            str[x][y] = '0';
        }
        return 0;
    }
    
    int main(){
        //每个状态已经填的数有几个
        for(int i = 0; i < 1<<9; i++)
            for(int j = i; j; j-=j&-j)cnt[i]++;
        //对于状态(1<<i),对应的数为'1'+num[1<<i];
        for(int i = 0; i < 9; i++)
            num[1<<i] = i;
        int T;  cin>>T;
        while(T--){
            //data in
            for(int i = 0; i < 9; i++)scanf("%s",str[i]);
            //初始化,都可以填。
            for(int i = 0; i < 9; i++)row[i] = col[i] = grid[i] = (1<<9)-1;
            tot = 0;
            //
            for(int i = 0; i < 9; i++)
                for(int j = 0; j < 9; j++)
                    if(str[i][j] != '0')flip(i,j,str[i][j]-'1');
                    else tot++;
            dfs(tot);
            //data out
            cout<<'
    ';
            for(int i = 0; i < 9; i++)
                printf("%s
    ",str[i]);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    c语言 414 根据输入的整数,循环显示1234567890
    c语言 47 编写一段程序,显示小于输入的整数的所有2的乘方。
    c语言49 交替输出正负号,当输入0以下时什么也不显示
    c语言48 改写48的程序,当输入的值小于1时不输出换行符
    c语言 411 逆向输出输入的整数值(同时输出原始数据)
    c语言47 显示出小于输入的整数的所有2的乘方
    c语言412 输入一个整数值显示其位数
    c语言415 输出标准身高体重对照表
    c语言413 求1到n的和
    c语言 410 输出连续* \n
  • 原文地址:https://www.cnblogs.com/shuaihui520/p/9879628.html
Copyright © 2020-2023  润新知