AcWing 166. 数独

223 阅读2分钟

目录:算法日记

题目来源:166. 数独 - AcWing题库

题目描述

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

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

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 8181 个字符,代表数独的 8181 个格内数据(顺序总体由上到下,同行由左到右)。 每个字符都是一个数字(191−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

算法思路

若把每种数独状态当作一个结点,即可使用搜索进行求解。但考虑到每个空位上可能存在99种情况,直接暴搜时间上必然爆炸,因此考虑剪枝优化。

  • 优化搜索顺序:从可填合法数字最少的格子开始搜索;
  • 排除等效冗余:每次都从可填合法数字最少的格子搜索,如果有多个格子可填合法数字相等,则搜索第一个格子,避免冗余;
  • 可行性剪枝:行、列、九宫格内数字不能出现重复(此处使用位运算优化);
    • 使用99位二进制数表示数字出现情况,11表示还没用,00表示用过了;
    • 判断某格子可填数字,只需要对行、列、九宫格的二进制数取交集,即进行与运算;
    • 使用lowbit映射快速求出当前格子可用数字;

AC代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 9;
const int M = 1 << N;
int ones[M], map[M];
int row[N], col[N], cell[3][3];
char str[100];

//行列单元格判重初始化
void init() {
    for(int i = 0; i < N; ++i) {
        row[i] = col[i] = (1 << N) - 1;
    }
    for(int i = 0; i < 3; ++i) {
        for(int j = 0; j < 3; ++j) {
            cell[i][j] = (1 << N) - 1;
        }
    }
}

//绘制
void draw(int x, int y, int t, bool is_set) {
    //修改字符串
    if(is_set) str[x * N + y] = t + '1';
    else str[x * N + y] = '.';
    
    //修改标记
    int v = 1 << t;
    if(!is_set) v = -v;
    
    row[x] -= v;
    col[y] -= v;
    cell[x / 3][y / 3] -= v;
}

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

int lowbit(int x) {
    return x & (-x);
}
bool dfs(int cnt) {
    if(!cnt) return true;
    
    int minv = 10;
    int x, y;
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < N; ++j) {
            if(str[i * N + j] == '.') {
                //有哪些数字可以使用
                int state = get(i, j);
                if(ones[state] < minv) {
                    minv = ones[state];
                    x = i, y = j;
                }
            }
        }
    }
    
    int state = get(x, y);
    for(int i = state; i; i -= lowbit(i)) {
        int t = map[lowbit(i)];
        draw(x, y, t, true);
        if(dfs(cnt - 1)) return true;
        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; //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(cnt);
        cout<<str<<endl;
    }
    return 0;
}