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("\n");
        }
    }
    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<<'\n';
        for(int i = 0; i < 9; i++)
            printf("%s\n",str[i]);
    }
    return 0;
}
View Code

 

posted @ 2018-10-30 21:16  shuai_hui  阅读(417)  评论(0编辑  收藏  举报