首页 > 其他分享 >Acwing166 数独题解 - DFS剪枝优化

Acwing166 数独题解 - DFS剪枝优化

时间:2024-03-10 14:44:06浏览次数:26  
标签:剪枝 int 题解 位置 DFS ++ str lowbit 数独

166. 数独 - AcWing题库

题意

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

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

思路

搜索+剪枝(优化搜索顺序、位运算)

  • 优化搜索顺序:很明显,我们肯定是从当前能填合法数字最少的位置开始填数字
  • 位运算:很明显这里面check判定很多,我们必须优化这个check,所以我们可以对于,每一行,每一列,每一个九宫格,都利用一个九位二进制数保存,当前还有哪些数字可以填写.
  • lowbit:我们这道题目当前得需要用lowbit运算取出当前可以能填的数字.

code + 详细注释

#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的操作 
    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; // 结束快乐~
}

作者:Hustle
链接:https://www.acwing.com/solution/content/57159/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:剪枝,int,题解,位置,DFS,++,str,lowbit,数独
From: https://www.cnblogs.com/Silly3kidZ/p/18064176

相关文章

  • [ABC219E] Moat 题解
    [ABC219E]Moat题解思路解析一眼看到输入数据只有\(4\)行\(4\)列,直接想到状压枚举。可以直接枚举所有护城河所包含起来的格子,判断是否连通以及判断是否包含住了所有村庄。判断连通我选择用洪水填充,随便选一个包含着的格子,若可以通过当前格移动到所有被包含格就说明连通。以......
  • ABC344G 题解
    ABC344G题解给定平面上\(n\)个点和\(q\)条直线,问对于每条线,有多少点在它上方。形式化的,对于直线\(y=ax+b\)统计有多少点\((x,y)\)满足\(y\geax+b\),即\(-ax+y\geb\)。故我们可以将所有点按照\(-ax+y\)排序,从而利用二分简单的得出结果。但是我们显然不可能暴力进......
  • 洛谷 P1099 题解
    洛谷P1099【NOIP2007提高组】树网的核题意简述给定一棵带边权无根树和一个正整数\(s\)。在这棵树的任意直径上截取一段长度不超过\(s\)的路径\(F\),使离\(F\)最远的点到\(F\)的距离最小,求出这个距离。思路记\(\delta(a,b)\)为\(a,b\)之间的路径。对于任意......
  • abc344_D - String Bags 题解
    一个月没有碰oi,感觉水平已经退化到负的了。来复健一下。D-StringBagslink题意:给你\(n\)组字符串组,按\(1\)~\(n\)的顺序,对于每组字符串组,可从中至多选一个字符串。求能否用所选串按顺序拼接成指定串,以及选取字符串的最小个数。然后读完题发现是个\(01\)背包;对于第......
  • AT_abc344_e 题解
    本文同步发表于洛谷。赌狗天天输的一集。赛时各种【数据删除】原因导致没做出来。大意给你一个长度为\(N\)的序列\(A=(A_1,\ldots,A_N)\)。保证\(A\)中的元素是不同的。你要处理\(Q\)个操作。每个操作是以下两种类型之一:1xy:在\(A\)中元素\(x\)后面紧接着插入......
  • AT_abc344_d 题解
    赌狗天天输的一集。大意你最开始有一个空字符串\(S\)。你还有编号为\(1,2,\dots,N\)的袋子,每个袋子都包含一些字符串。袋子\(i\)包含\(A_i\)个字符串\(S_{i,1},S_{i,2},\dots,S_{i,A_i}\)。对\(i=1,2,\dots,N\)重复以下步骤仅一次(这里原题没有讲清楚):......
  • P10238 [yLCPC2024] F. PANDORA PARADOXXX 题解
    分析考虑时光倒流。对于需要合并的两个连通块\(x,y\),其合并之后的最远点对距离一定是合并之前的两组点对中产生的。在合并的时候枚举点对,取距离最大值即可。由于我们是倒着来的,所有连通块的最远点对距离最大值不减,所以能直接在合并之后取最大值。维护连通块用并查集即可。复杂......
  • P10237 [yLCPC2024] E. Latent Kindom 题解
    分析一眼了非最优解。考虑二分答案。对于二分出来的中位数\(x\),到\(a_i\)和\(a_j\)里边又去二分。得到两个序列中不超过\(x\)的数的数量。若这个数量\(cnt\ge\lceil\frac{len_{i}+len_{j}}{2}\rceil\),则\(x\)可能成为中位数,然后继续二分即可。把序列离散化,复杂度......
  • DFS-分成互质组
    1118.分成互质组-AcWing题库题意将给定数组a分割成若干个互质子集的最小子集数量。每个子集内的任意两个元素都互质。疑惑我根据AcWing1118.分成互质组-AcWing题解的思路2写的代码如下,但是代码有错。把第35行g[--zushu].pop_back();注释掉之后会出现terminate......
  • [ABC219F] Cleaning Robot 题解
    [ABC219F]CleaningRobot题解思路解析要点:将整个图拆分成每一轮的每一个点单独考虑贡献。首先看到\(k\le10^{12}\)发现不能直接枚举\(k\)轮,于是开始找每一轮的规律。首先可以知道,如果操作固定,那么起点和路径上每一个点以及终点的相对位置不会改变。也就是说每一轮的起......