acwing 166 数独【DFS、剪枝与优化】

数独是一种传统益智游戏,你需要把一个9 × 9的数独补充完整,使得图中每行、每列、每个3 × 3的九宫格内数字1~9均恰好出现一次。

请编写一个程序填写数独。

输入格式
输入包含多组测试用例。

每个测试用例占一行,包含81个字符,代表数独的81个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字(1-9)或一个”.”(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词“end”的单行,表示输入结束。

输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:

4…8.5.3…7…2…6…8.4…1…6.3.7.5…2…1.4…
…52…8.4…3…9…5.1…6…2…7…3…6…1…7.4…3.
end

输出样例:

417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

分析:

1、搜索顺序:优先枚举可选数少的格子,俩for循环找出可选数最少的格子来dfs
2、优化:用位运算去记录每行每列和每个小九宫格的选数状态,利用&运算。

#include 

using namespace std;

const int N = 9 , M  = 1 << N ;

char str[100];
int row[N] , col[N] , cel[3][3];
int ones[M] , map[N] ;

void init(){ //初始化为111111111
    for(int i = 0 ; i < 3 ; i ++)
    {
        for(int j = 0 ; j < 3 ; j ++)
        {
            row[i*3+j] = col[i*3+j] = cel[i][j] = M - 1;
        }
    }
}

void draw(int x,int y ,int t , int state){ //更新str的字符
    int v = 1 << t;
    if(state)  str[x*N + y] = t + '1';
    else
    {
        v = -v;
        str[x*N + y] = '.';
    }
    row[x] -= v;
    col[y] -= v;
    cel[x/3][y/3] -= v;
    
}

int lowbit(int i){
    return i & -i; 
}

int get(int x, int y){
    return row[x] & col[y] & cel[x/3][y/3];
}

bool dfs(int u )
{
    if( u == 0 ) return true;
    
    //寻找可选数最少的'.'格子
    int mins = 10;
    int x , y;
    for(int i = 0 ; i < N ; i ++)
    {
        for(int j = 0 ; j < N ; j ++)
        {
            if( str[i * N + j] == '.') 
            {
                int t = get(i,j);
                if( ones[t] < mins )
                {
                    mins = ones[t];
                    x = i , y = j;
                }
            }

        }
    }
    
    for( int i = get(x,y) ; i ; i -= lowbit(i) )
    {
        int t = map[lowbit(i)];
        draw(x,y,t,true);
        if( dfs(u - 1)  ) return true;  // 一找到就直接退出dfs
        draw(x,y,t,false); //恢复现场
    }
    
    return false;
}

int main(){
    for(int i = 0 ; i < N ; i ++) map[1 << i] = i ; //求出每个位置单独的1,对应了哪个数
    for(int i = 0 ; i < M ; i ++)
        for(int j = 0 ; j < N ; j ++)
            ones[i] += i >> j & 1; //计算出0 - 111111111 中 每个数的1的个数,也就是可选数的个数
            
    while( cin >> str , str[0] != 'e' )
    {
        init();
        int cnt  = 0 ;
        for(int i = 0 , k = 0 ; i < N ; i ++)
        {
            for(int j = 0 ; j < N ; j ++ , k ++)
            {
                if(str[k] != '.')
                {
                    int t = str[k] - '1';
                    draw(i,j,t,true);
                }
                else cnt ++; //记录下所有需要填数的'.'格子,作为dfs的终止条件
            }
        }
        
        dfs(cnt);
        
        puts(str);
    }
    
    return 0;
}

你可能感兴趣的:(算法)