AcWing166. 数独-DFS剪枝与优化

题目

思路

  1. 思考问题:搜索顺序->考虑剪枝
  2. 搜索顺序:先随意选择一个空格子,枚举该格子可填写的数字,当所有格子都填完的时候,说明可以退出了
  3. 剪枝:
    1. 优化搜索顺序:随意选择一个空格子:应该优先搜索分支数量较少的方案,如果分支数量相同,则选择前者
    2. 可行性剪枝:当前数字不能与行,列,九宫格有重复
  4. 本题用到了位运算优化:9位的01串:0表示尚未用过,1表示用过;与运算
       行:123456789
    行01:001101010
    列01:...
    九01:...
    lowbit运算:返回01串的1
  5. AcWing 801. 二进制中1的个数 - AcWing

代码

#include <iostream>

#define lowbit(x) (x & -x) // lowbit操作
#define get(x, y) (row[x] & col[y] & cell[x / 3][y / 3]) // get(x, y) 找到该位置可以填哪些数的状态

using namespace std;

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

int one[M], map[M]; // one[state]为该state中有几个1, map[state]为state对应的十进制值
int col[N], row[N], cell[3][3];
char str[100];

void init() { // 初始化(将所有位置都初始化可以填数的状态)
    for (int i = 0; i < N; ++ i) row[i] = col[i] = (1 << N) - 1; 
    // 将行和列都用二进制来优化(刚开始的位置都为1)

    for (int i = 0; i < 3; ++ i)
        for (int j = 0; j < 3; ++ j)
            cell[i][j] = (1 << N) - 1; // 每个3 * 3的小方格也用二进制来优化(刚开始也都为1)
}

void draw(int x, int y, int t, bool is_set) { // 在(x, y)的位置上(is_set)<是/否>填t的操作,
// t 是待填入或待删去的二进制字符串state的下标(0..8) 
//t + ‘1’ 是因为由于你传入的是二进制字符串state的下标,所以 draw 如果是填入操作,那么即需要 t + ‘1’
    if (is_set) str[x * N + y] = '1' + t; // 如果填数的话, 将该数转换为字符形式填入字符串中对应的位置
    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; // 在这个原数对应的小方格减去该数的二进制数
}

bool dfs(int cnt) {
    if (!cnt) return true; // 知道没有位置能填数就结束搜索

    int minv = 10; // 记录当前最少枚举方案
    int x, y; // x, y记录枚举方案最少的位置的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(one[state] < minv) { // 只有当当前位置的方案少于当前最少方案才有搜索的必要
                    x = i, y = j;
                    minv = one[state];
                }
            }

    int state = get(x, y); // 找到最少枚举方案对应的位置的能填的数的状态
    for (int i = state; i; i -= lowbit(i)) { // 枚举该位置上能填的数,用lowbit操作
        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; // 预处理map[]

    for (int i = 0; i < 1 << N; ++ i)
        for (int j = 0; j < N; ++ j)
            one[i] += (i >> j & 1); // 预处理one[]

    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); // 开始搜索

        puts(str); // 输出答案
    }

    return 0; // 结束ing~
}
  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值