最终代码地址:https://github.com/laiy/Datastructure-Algorithm/blob/master/sicily/1317.c

这题博主刷了1天,不是为了做出来,AC之后在那死磕性能...

累积交了45份代码,纪念一下- -

以上展示了从1.25s优化到0.03s的艰苦历程...

来看题目吧,就是一个数独求解的题:

1317. Sudoku

Constraints

Time Limit: 10 secs, Memory Limit: 32 MB

Description

Sudoku is a placement puzzle. The goal is to enter a symbol in each cell of a grid, most frequently a 9 x 9 <tex2html_verbatim_mark>grid made up of 3 x 3 <tex2html_verbatim_mark>subgrids. Each row, column and subgrid must contain only one instance of each symbol. Sudoku initially became popular in Japan in 1986 and attained international popularity in 2005.

<tex2html_verbatim_mark>

The word Sudoku means ``single number" in Japanese. The symbols in Sudoku puzzles are often numerals, but arithmetic relationships between numerals are irrelevant.

According to wikipedia:

The number of valid Sudoku solution grids for the standard 9 x 9 <tex2html_verbatim_mark>grid was calculated by Bertram Felgenhauer in 2005 to be 6,670,903,752,021,072,936,960, which is roughly the number of micrometers to the nearest star. This number is equal to 9! * 72aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAPBAMAAAG6CmjSAAAAFVBMVEXd3d3u7u5mZmb///9ERER3d3e7u7uBh2/jAAAAAXRSTlMAQObYZgAAAEdJREFUCNddi7ENgDAQxCxlBMQAEQscmGSR338XmlBAYbsyMhFRIgbDDIKapVHBGkH9coutYRV6or7bv9dqPww6tt2g3Rx5AAybFDFi+ZowAAAAAElFTkSuQmCC" alt="$\scriptstyle \wedge$" align="bottom" border="0" />* 2aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAPBAMAAAG6CmjSAAAAFVBMVEXd3d3u7u5mZmb///9ERER3d3e7u7uBh2/jAAAAAXRSTlMAQObYZgAAAEdJREFUCNddi7ENgDAQxCxlBMQAEQscmGSR338XmlBAYbsyMhFRIgbDDIKapVHBGkH9coutYRV6or7bv9dqPww6tt2g3Rx5AAybFDFi+ZowAAAAAElFTkSuQmCC" alt="$\scriptstyle \wedge$" align="bottom" border="0" />* 27, 704, 267, 971 <tex2html_verbatim_mark>, the last factor of which is prime. The result was derived through logic and brute force computation. The number of valid Sudoku solution grids for the 16 x 16 <tex2html_verbatim_mark>derivation is not known.

Write a program to find a solution to a 9 x 9 <tex2html_verbatim_mark>Sudoku puzzle given a starting configuration.

Input

The first line will contain an integer specifying the number of puzzles to be solved. The remaining lines will specify the starting configuration for each of the puzzles. Each line in a starting configuration will have nine characters selected from the numerals 1-9 and the underscore which indicates an empty cell.

Output

For each puzzle, the output should specify the puzzle number (starting at one) and describe the solution characteristics. If there is a single solution, it should be printed. Otherwise, a message indicating whether there are no solutions or multiple solutions should be printed. The output should be similar to that shown below. All input cases have less than 10,000 solutions.

Sample Input

3
________4
1____9_7_
__37_28__
____7_26_
4_______8
_91_6____
__42_36__
_3_14___9
9________
7_9__2___
3_____891
___39___4
48__6____
__5___6__
____4__23
2___57___
568_____7
___8__4_2
82_______
___5__2__
__6_4_7__
_5___1_7_
9_2_5_4_1
_3_8_6_9_
__3_6_1__
__5__2___
_______34

Sample Output

Puzzle 1 has 6 solutions

Puzzle 2 solution is
719482365
324675891
856391274
482563719
135729648
697148523
243957186
568214937
971836452 Puzzle 3 has no solution

刚看到这题,思路很清楚,启发式DFS,按每个空格可以填入数字的可能数量作为权重,每次取权重最小的搜索空间进行拓展,这样可以在很大程度上保证剪枝剪的是最大的。

比如,现在有5个搜索空间(即5个没有填入数字的空格),每个空间的权重为(1,2,3,4,5)。

那么DFS的时候树伸出去的枝叶我们喜欢尽量的少,这样在递归的时候回溯的时候剪掉的枝叶会更多,而每个职业延伸出去的代价可以认为是一样的。

假设从根到叶延伸(式子中从左到右)的形式为1 * 2 * 3 * 4 * 5,那么在第二个空间搜索的时候如果回溯了是不是剪掉了一半的枝?

而如果反过来搜5 * 4 * 3 * 2 * 1这样来搜的话回溯只是剪掉了1/5的枝。

这个原理和马周游的Warnsdorff's rule其实是一样的。

然后来考虑一下数据结构,为了方便计算,我们用3个数组来记录每个行,列,块数字的占用情况。(row_space, col_space, block_space)

用board来记录当前数独填入状态,在用nodes来记录搜索空间的权重,比如nodes[i][j] = 4意味着第i行第j列权重为4。

代码如下:

 #include <cstdio>
#include <cstring> short board[][];
bool col_space[][], row_space[][], block_space[][][];
short record[][];
short nodes[][];
int solutions, i, j, min, weight, k, m, v, c, update_value; inline void update_weight(int &i, int &j) {
if (nodes[i][j] == - || row_space[i][update_value] || col_space[j][update_value] || \
block_space[(i - ) / ][(j - ) / ][update_value])
return;
nodes[i][j]--;
} inline void count_weight(int i, int j) {
static int record_i, record_j;
record_i = i, record_j = j;
for (m = ; m < ; m++)
if (m != record_j)
update_weight(i, m);
for (m = ; m < ; m++)
if (m != record_i)
update_weight(m, j);
i = ((i - ) / ) * + ;
j = ((j - ) / ) * + ;
for (m = i; m < i + ; m++)
for (v = j; v < j + ; v++)
if (m != record_i && v != record_j)
update_weight(m, v);
} inline void heuristic_dfs() {
if (!c) {
if (!solutions)
memcpy(record, board, sizeof(board));
solutions++;
return;
}
min = ;
short record_i, record_j, k, board_record_i, board_record_j, temp[][];
for (i = ; i <= ; i++)
for (j = ; j <= ; j++)
if (nodes[i][j] != - && nodes[i][j] < min)
min = nodes[i][j], record_i = i, record_j = j;
board_record_i = (record_i - ) / , board_record_j = (record_j - ) / ;
c--;
nodes[record_i][record_j] = -;
memcpy(temp, nodes, sizeof(nodes));
for (k = ; k < ; k++)
if (!(row_space[record_i][k] || col_space[record_j][k] || block_space[board_record_i][board_record_j][k])) {
update_value = k;
count_weight(record_i, record_j);
row_space[record_i][k] = col_space[record_j][k] = block_space[board_record_i][board_record_j][k] = true;
board[record_i][record_j] = k;
heuristic_dfs();
row_space[record_i][k] = col_space[record_j][k] = block_space[board_record_i][board_record_j][k] = false;
memcpy(nodes, temp, sizeof(nodes));
}
c++;
nodes[record_i][record_j] = min;
} int main() {
int t, count = ;
scanf("%d", &t);
char input[];
while (t--) {
if (count != )
printf("\n");
memset(board, -, sizeof(board));
memset(nodes, -, sizeof(nodes));
memset(col_space, , sizeof(col_space));
memset(row_space, , sizeof(row_space));
memset(block_space, , sizeof(block_space));
solutions = ;
for (i = ; i <= ; i++) {
scanf("%s", input);
for (j = ; j < ; j++) {
if (input[j] != '_')
board[i][j + ] = input[j] - '', row_space[i][board[i][j + ]] = true, col_space[j + ][board[i][j + ]] = true, \
block_space[(i - ) / ][(j) / ][board[i][j + ]] = true;
}
}
c = ;
for (i = ; i <= ; i++)
for (j = ; j <= ; j++)
if (board[i][j] == -) {
weight = ;
for (k = ; k < ; k++)
if (row_space[i][k] || col_space[j][k] || block_space[(i - ) / ][(j - ) / ][k])
weight--;
nodes[i][j] = weight;
c++;
}
heuristic_dfs();
if (!solutions)
printf("Puzzle %d has no solution\n", count++);
else if (solutions > )
printf("Puzzle %d has %d solutions\n", count++, solutions);
else {
printf("Puzzle %d solution is\n", count++);
for (i = ; i <= ; i++) {
for (j = ; j <= ; j++)
printf("%d", record[i][j]);
printf("\n");
}
}
}
return ;
}

这种思路不断做下去极限是0.16s。

那博主开头的0.03s是怎么做到的呢?

答案是:用matrix67的八皇后那样用位运算提高状态空间计算的效率直接暴搜就可以了。

事实确实如此!(我受到了惊吓,启发式DFS比DFS慢这么多)

我要代码按我的启发式思路走下去在数据结构和执行性能上打了很大折扣,我需要额外的计算来维护启发式的执行,如果启发式带来的优势不能大于算法变复杂带来的劣势的话会得不偿失!

来考虑一下为了执行启发式需要的代价:

1. 初始所有状态的weight,遍历所有结点。

2. 每次dfs选取最小weight结点的代价。

3. 每次延伸枝的时候我们更改了某一个结点的状态,那么对应的行,列,块的结点依次需要判断是否需要更新weight。

而事实证明维护以上状态需要的计算带来的负担是大于启发式带来剪枝优化的收益的。

接下来介绍一下什么是位运算状态暴搜。

其实就是用位来描述状态,语义是由人来赋予的。这里用9位来描述某一行/列/块来描述这行/列/块数字的占用情况。

比如row_space[0] = 111111111,代表第一行9个数字都是可以搜索的空间,如果变成111111110则表示第一行的1这个数字已经被占用。

这么做有什么好处?效率和空间都暴涨!

如何获得当前结点可以搜索的空间?

space = row_space[i] & col_space[j] & block_space[block_index]就行了不是吗?

如何遍历搜索空间?

用space & (-space)来提取出最后一个1代表的搜索空间,执行,然后用异或运算来更新状态空间即可。

这样暴搜性能可以达到0.03s!

代码如下:

 #include <cstdio>
#include <cstring> int solutions;
int row_space[], col_space[], block_space[];
int board[][], record[][];
int digit_table[( << ) + ]; void dfs(int i, int j) {
while (i >= && board[i][j])
if (j)
j--;
else
i--, j = ;
if (i == -) {
if (!solutions)
memcpy(record, board, sizeof(board));
solutions++;
return;
}
int block_index = i / * + j / ;
int space = row_space[i] & col_space[j] & block_space[block_index];
int put;
while (space) {
put = space & (-space);
space ^= put;
board[i][j] = digit_table[put];
row_space[i] ^= put;
col_space[j] ^= put;
block_space[block_index] ^= put;
dfs(i, j);
row_space[i] ^= put;
col_space[j] ^= put;
block_space[block_index] ^= put;
}
board[i][j] = ;
} int main() {
int t, count = , i, j, init, mask;
scanf("%d", &t);
char input[];
init = ( << ) - ;
for (int i = ; i < ; i++)
digit_table[ << i] = i + ;
while (t--) {
if (count != )
printf("\n");
solutions = ;
for (i = ; i < ; i++)
row_space[i] = col_space[i] = block_space[i] = init;
memset(board, , sizeof(board));
for (i = ; i < ; i++) {
scanf("%s", input);
for (j = ; j < ; j++)
if (input[j] != '_') {
board[i][j] = input[j] - '';
mask = ~( << (board[i][j] - ));
row_space[i] &= mask;
col_space[j] &= mask;
block_space[i / * + j / ] &= mask;
}
}
dfs(, );
if (!solutions)
printf("Puzzle %d has no solution\n", count++);
else if (solutions > )
printf("Puzzle %d has %d solutions\n", count++, solutions);
else {
printf("Puzzle %d solution is\n", count++);
for (i = ; i < ; i++) {
for (j = ; j < ; j++)
printf("%d", record[i][j]);
printf("\n");
}
}
}
return ;
}

Sicily1317-Sudoku-位运算暴搜的更多相关文章

  1. 【UVA】658 - It&#39;s not a Bug, it&#39;s a Feature!(隐式图 + 位运算)

    这题直接隐式图 + 位运算暴力搜出来的,2.5s险过,不是正法,做完这题做的最大收获就是学会了一些位运算的处理方式. 1.将s中二进制第k位变成0的处理方式: s = s & (~(1 < ...

  2. 模拟赛T5 : domino ——深搜+剪枝+位运算优化

    这道题涉及的知识点有点多... 所以还是比较有意思的. domino 描述 迈克生日那天收到一张 N*N 的表格(1 ≤ N ≤ 2000),每个格子里有一个非 负整数(整数范围 0~1000),迈克 ...

  3. HUST 1605 Gene recombination(广搜,位运算)

    题目描述 As a gene engineer of a gene engineering project, Enigma encountered a puzzle about gene recomb ...

  4. php实现不用加减乘除号做加法(1、善于寻找资源:去搜为什么位运算可以实现加法,里面讲的肯定要详细一万倍)

    php实现不用加减乘除号做加法(1.善于寻找资源:去搜为什么位运算可以实现加法,里面讲的肯定要详细一万倍) 一.总结 1.善于寻找资源:去搜为什么位运算可以实现加法,里面讲的肯定要详细一万倍 二.ph ...

  5. POJ - 3074 Sudoku (搜索)剪枝+位运算优化

    In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For exa ...

  6. UVA 565 565 Pizza Anyone? (深搜 +位运算)

      Pizza Anyone?  You are responsible for ordering a large pizza for you and your friends. Each of th ...

  7. POJ 1753 位运算+枚举

    题意: 给出4*4的棋盘,只有黑棋和白棋,问你最少几步可以使棋子的颜色一样. 游戏规则是:如果翻动一个棋子,则该棋子上下左右的棋子也会翻一面,棋子正反面颜色相反. 思路: 都是暴搜枚举. 第一种方法: ...

  8. c++20701除法(刘汝佳1、2册第七章,暴搜解决)

    20701除法 难度级别: B: 编程语言:不限:运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述     输入正整数n,按从小到大的顺序输出所有 ...

  9. N皇后问题(位运算实现)

    本文参考Matrix67的位运算相关的博文. 顺道列出Matrix67的位运算及其使用技巧 (一) (二) (三) (四),很不错的文章,非常值得一看. 主要就其中的N皇后问题,给出C++位运算实现版 ...

随机推荐

  1. 24种设计模式--观察者模式【Observer Pattern】

     <孙子兵法>有云: “知彼知己,百战不殆:不知彼而知己,一胜一负:不知彼,不知己,每战必殆”,那怎么才能知己知彼呢?知己是很容易的,自己的军队嘛,很容易知道,那怎么知彼呢?安插间谍是很好 ...

  2. java中运算符——进度1

    Class Demo1{    public static void main(String[] args) {        /*        一.逻辑运算法用于连接两个boolean类型的表达式 ...

  3. PHP-Wamp集成包安装教程

    在Windows平台上搭建PHP的开发环境可以下载WAMP(Windows.Apache.MySQL.PHP的首字母缩写)集成化安装包.这样就不需要单独安装Apache.MySQL和PHP了. 这款软 ...

  4. PHP源代码分析(第一章):Zend HashTable详解【转】

    转载于http://www.phppan.com/2009/12/zend-hashtable/ 在PHP的Zend引擎中,有一个数据结构非常重要,它无处不在,是PHP数据存储的核心,各种常量.变量. ...

  5. 关于Java(不同工具或平台与“Hello World”)

    对于任何编程语言,都最常见的入门应用: Hello World NetBeans 和 “Hello World” 编写 Java 程序前,先要准备好: Java SE Development Kit ...

  6. phpwind9.0 顶部和底部版权信息永久性修改

    过了pw头部和底部版权修改方法,但是每次升级程序后版权又变成了默认的了,还得重新修改,其实有个方法可以永久性修改,底部和顶部随着主题走. pw9全局主题位于/themes/site/目录下,  前面文 ...

  7. [科普贴]为何Flash被淘汰?附Chrome看视频最完美教程!

    Adobe 公司放弃 移动 平台的 Flash 支持已经是板上钉钉的事了, Google Play 的 Flash 插件也会在 8 月份下架,这在一定程度上也会促进 HTML5 的发展和普及,因此我个 ...

  8. 设计模式之单例(singleton)设计模式代码详解

    单例有两种:懒汉式和饿汉式 /** * 懒汉式的单例模式 * 这种单例模式如果采用到多线程调用该方法,有可能会产生多个实例,原因是: * 当线程一进入了①处,此时轮到线程二的时间片,线程二也来到①处, ...

  9. Mozilla研究—深入理解mozilla所需的背景知识

    mozilla是一个以浏览器为中心的软件平台,它在我们平台中占有重要地位.我们用它来实现WEB浏览器.WAP浏览器.邮件系统.电子书和帮助阅读器等应用程序.为此,我最近花了不少时间去阅读mozilla ...

  10. 控制台程序使用MFC类的方法

    (unresolved external symbol __endthreadex解决办法) 1.新建控制台程序: 2.添加源代码如下: #include <afx.h> #include ...