靶形数独

靶形数独与普通数独的区别在于,它具有权重,我们需要求出某种意义上的权重最大值。

直接上例题:

小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。

但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。

靶形数独的方格同普通数独一样,在 9×9 的大九宫格中有 9 个 3×3 的小九宫格(用粗黑色线隔开的)。

在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 到 9 的数字。

每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。

但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高(如下图所示)。

上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红色区域)每个格子为 9 分,再外面一圈(蓝色区域)每个格子为 8 分,蓝色区域外面一圈(棕色区域)每个格子为 7 分,最外面一圈(白色区域)每个格子为 6 分,如上图所示。

比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。

而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。

如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829。

游戏规定,将以总分数的高低决出胜负。

由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。

输入格式 输入一共包含 9 行。

每行 9 个整数(每个数都在 0—9 的范围内),表示一个尚未填满的数独方格,未填的空格用 0 表示。

每两个数字之间用一个空格隔开。

输出格式 输出可以得到的靶形数独的最高分数。

如果这个数独无解,则输出整数 −1。

数据范围 40% 的数据,数独中非 0 数的个数不少于 30。

80% 的数据,数独中非 0 数的个数不少于 26。

100% 的数据,数独中非 0 数的个数不少于 24。

输入样例:

7 0 0 9 0 0 0 0 1
1 0 0 0 0 5 9 0 0
0 0 0 2 0 0 0 8 0
0 0 5 0 2 0 0 0 3
0 0 0 0 0 0 6 4 8
4 1 3 0 0 0 0 0 0
0 0 7 0 0 2 0 9 0
2 0 1 0 6 0 8 0 4
0 8 0 5 0 4 0 1 2

输出样例:

2829

解决这一题的关键在于,不要被复杂的外表所迷惑,本题和上一节中发表过的数独问题其实是同父异母的亲兄弟。

我们在上一题中,能找到数独的一组解,在这个问题中,我们就可以通过减少一个递归出口

(if(dfs(cnt - 1)) return true;
-->
dfs(cnt - 1)

使得dfs遍历可以完全进行,从而找到数独的所有解,并比较最大值。

如何计算分数值?

为了方便书写代码,我在这里,将整个数独划分为5个有重叠的区域:

总分值就等于:

绿框内所有元素 * 6+ 粉框内所有元素 + 紫框内所有元素 + 黄框内所有元素 +橙框元素

于是代码表现为:

int compute()
{int sum = 0;for(int i = 0; i < N; i++)for(int j = 0; j < N; j++)sum += 6 * sudu[i][j];for(int k = 1; k < 5; k++)for(int i = k; i < N - k; i++)for(int j = k; j < N - k; j++)sum += sudu[i][j];return sum;
}

其余代码与上一题就是大同小异啦~

#include <iostream>
#include <algorithm>using namespace std;const int N = 9;
int ones[1 << N], map[1 << N];
int row[N], col[N], cell[3][3];
int sudu[N][N];
int res;int compute()
{int sum = 0;for(int i = 0; i < N; i++)for(int j = 0; j < N; j++)sum += 6 * sudu[i][j];for(int k = 1; k < 5; k++)for(int i = k; i < N - k; i++)for(int j = k; j < N - k; j++)sum += sudu[i][j];return sum;
}inline int get(int x, int y)
{return row[x] & col[y] & cell[x / 3][y / 3];
}
inline int lowbit(int x)
{return x & -x;
}void print()
{for(int i = 0; i < N; i++, cout << endl)for(int j = 0; j < N; j++)cout << sudu[i][j] << ' ';
}
void dfs(int cnt)
{if(!cnt){int now = compute();print();if(now > res)res = now;return;}int minv = 10, x, y;for(int i = 0; i < N; i++)for(int j = 0; j < N; j++){if(sudu[i][j]) continue;int t = ones[get(i, j)];if(t < minv){minv = t;x = i, y = j;}}for(int state = get(x, y); state; state -= lowbit(state)){int put = map[lowbit(state)];row[x] -= 1 << put;col[y] -= 1 << put;cell[x / 3][y / 3] -= 1 << put;sudu[x][y] = put + 1;dfs(cnt - 1);row[x] += 1 << put;col[y] += 1 << put;cell[x / 3][y / 3] += 1 << put;sudu[x][y] = 0;}
}int main()
{for(int i = 0; i < N; i++)col[i] = row[i] = (1 << N) - 1;for(int i = 0; i < 3; i++)for(int j = 0; j < 3; j++)cell[i][j] = (1 << N) - 1;for(int i = 0; i < N; i++)map[1 << i] = i;for(int i = 0; i < 1 << N; i++){int s = 0;for(int j = i; j; j -= lowbit(j))s ++;ones[i] = s;}for(int i = 0; i < N; i++)for(int j = 0; j < N; j++)cin >> sudu[i][j];int cnt = 0;for(int i = 0; i < N; i++)for(int j = 0; j < N; j++)if(sudu[i][j]){row[i] -= 1 << (sudu[i][j] - 1);col[j] -= 1 << (sudu[i][j] - 1);cell[i / 3][j / 3] -= 1 << (sudu[i][j] - 1);}else cnt ++;dfs(cnt);cout << res;
}

16*16 数独

9*9数独和16*16数独的复杂度远远不止一个数量级,这要求我们使用更优化的剪枝方式实现,对算法提出了更高的要求。

当你代码老是调试不出来的时候,尝试去想一想,是不是基本数据结构出了问题。

对于高级数据结构,如string,其内部内存分配你可能是不知道的,对于此类数据结构,memcpy等内存操作会出问题,且问题非常隐蔽。

memset最好只用它来赋值0、0x3f3f3f3f等特殊值,一般值千万别用,否则会出问题,有时会统统变成-1。

——一个对这道题调试了一整天的笔者说到

例题:16*16数独

请你将一个 16×16 的数独填写完整,使得每行、每列、每个 4×4 十六宫格内字母 A∼P 均恰好出现一次。

保证每个输入只有唯一解决方案。

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

每组测试用例包括 16 行,每行一组字符串,共 16 个字符串。

第 i 个字符串表示数独的第 i 行。

字符串包含字符可能为字母 A∼P 或 -(表示等待填充)。

测试用例之间用单个空行分隔,输入至文件结尾处终止。

输出格式 对于每个测试用例,均要求保持与输入相同的格式,将填充完成后的数独输出。

每个测试用例输出结束后,输出一个空行。

输入样例

--A----C-----O-I
-J--A-B-P-CGF-H-
--D--F-I-E----P-
-G-EL-H----M-J--
----E----C--G---
-I--K-GA-B---E-J
D-GP--J-F----A--
-E---C-B--DP--O-
E--F-M--D--L-K-A
-C--------O-I-L-
H-P-C--F-A--B---
---G-OD---J----H
K---J----H-A-P-L
--B--P--E--K--A-
-H--B--K--FI-C--
--F---C--D--H-N-

输出样例

FPAHMJECNLBDKOGI
OJMIANBDPKCGFLHE
LNDKGFOIJEAHMBPC
BGCELKHPOFIMAJDN
MFHBELPOACKJGNID
CILNKDGAHBMOPEFJ
DOGPIHJMFNLECAKB
JEKAFCNBGIDPLHOM
EBOFPMIJDGHLNKCA
NCJDHBAEKMOFIGLP
HMPLCGKFIAENBDJO
AKIGNODLBPJCEFMH
KDEMJIFNCHGAOPBL
GLBCDPMHEONKJIAF
PHNOBALKMJFIDCEG
IAFJOECGLDPBHMNK

这题将在专题(1)的数独的基础上,继续剪枝,使得运行效率达到题目要求。

思考:还能怎么剪?

在以前的数独中,我们是从每一个小格出发,以局部的视角,完成数独的深度优先搜索,在16*16的剪枝中,我们需要从全局的角度,在一次dfs内填写所有当前已经确定可以填写的格子,或遇到某种特殊情况已经不可能再继续填写下去时,立刻回溯。

总结下来,还能做以下4种剪枝:

  1. 对某一空格而言,如果该空格尚未填写,但该空格无法填写,则应当回溯;如果该空格只有一种填写方式,则应该马上将其填写完毕。

  2. 对某一行而言,如果该行有一个字母已经由于条件束缚不可能被填写,则应当回溯;如果该行有一个字母只有一个地方可以填写,则应该马上将其填写完毕。

  3. 对某一列而言,如果该列有一个字母已经由于条件束缚不可能被填写,则应当回溯;如果该行有一个字母只有一个地方可以填写,则应该马上将其填写完毕。

  4. 对某一四宫格而言,如果四宫格有一个字母已经由于条件束缚不可能被填写,则应当回溯;如果该行有一个字母只有一个地方可以填写,则应该马上将其填写完毕。

如何判断有字母已经无法填写或者某字母只有一种方式可以填写?

本题虽为数独题,但其中运用到了大量的位运算知识,是一道对位运算训练非常到位的题目,本题的思想在以前动态规划求解石板切割问题中大致也巧合地运用上了。

如何判断存在字母都无法填写 == 如何判断所有字母都可以填写,可以考虑使用按位或操作,求并集,将并集与满集做比较,倘若并集不等于满集,说明有元素确实无法被填写。则应该回溯。

sor |= state[i][j];

如何判断某字母只有一种方式可以填写?

可以考虑使用按位与操作,假如某一字母可以填写的次数出现了两次,则将其在全集中删除,最终如果全集有剩下的元素,说明它们都是只出现了一次的元素,则可直接填写。

sand &= ~(sor & state[i][j]);

在数独那一题的基础上,再加上以上四个剪枝,并维护一个state二维数组来保存每个位置的可以存放的字母的二进制表示,并注意恢复现场,这一题就大功告成了。

这一题是我写过最长的一道算法题,我debug也de了整整一天,主要原因在于这个bug十分隐蔽,我使用了string数组来保存数独中的字母,但是,string属于高级数据结构,它的内存分配并不是固定的,所以后来使用memcpy时就出现了问题。

数独四题对dfs、位运算等要求极高,推荐大家有空都挑战一下

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 16;
int ones[1 << N], map[1 << N];
char sudu[N][N + 1];
int state[N][N];//存放每一格能放什么
int bstate[N * N + 1][N][N];
int bstate2[N * N + 1][N][N];
char bsudu[N * N + 1][N][N + 1];inline int lowbit(int x)
{return x & -x;
}void draw(int x, int y, int c)//将c(所代表的字母)填入x,y位置并维护state
{for(int i = 0; i < N; i ++){state[x][i] &= ~(1 << c);state[i][y] &= ~(1 << c);}int sx = x / 4 * 4, sy = y / 4 * 4; //(sx, sy)对应其子块开始坐标for(int i = 0; i < 4; i ++)for(int j = 0; j < 4; j ++){state[sx + i][sy + j] &= ~(1 << c);}sudu[x][y] = c + 'A';state[x][y] = 1 << c;return;
}bool dfs(int cnt)
{if(!cnt) return true;/*剪枝1:如果某一空格只能填1个或不能填剪枝2:对于每一行,如果某个字母都不能出现或只能出现一次剪枝3:对于每一列,如果某个字母都不能出现或只能出现一次剪枝4:对于每一块,如果某个字母都不能出现或只能出现一次递归出口优化:从可填数最少处开始填*///便于保护现场所做的处理int kcnt = cnt;memcpy(bstate[kcnt], state, sizeof state);memcpy(bsudu[kcnt], sudu, sizeof sudu);//剪枝1:如果某一空格只能填1个或不能填for(int i = 0; i < N; i ++)for(int j = 0; j < N; j ++)if(sudu[i][j] == '-')if(state[i][j] == 0){memcpy(state, bstate[kcnt], sizeof state);memcpy(sudu, bsudu[kcnt], sizeof sudu);//恢复现场return false;}else if(ones[state[i][j]] == 1){draw(i, j, map[state[i][j]]);cnt --;}//剪枝2:对于每一行,如果某个字母都不能出现或只能出现一次for(int i = 0; i < N; i ++){int sor = 0, sand = (1 << N) - 1;int drawn = 0;for(int j = 0; j < N; j ++){sand &= ~(state[i][j] & sor);sor |= state[i][j];if(sudu[i][j] != '-') drawn |= state[i][j];}if(sor != (1 << N) - 1)//存在有不能出现的字母{memcpy(sudu, bsudu[kcnt], sizeof sudu);memcpy(state, bstate[kcnt], sizeof state);return false;}for(int k = sand; k; k -= lowbit(k)){int put = lowbit(k);if(!(drawn & put))//未被填过,填{for(int j = 0; j < N; j ++){if(state[i][j] & put){draw(i, j, map[put]);cnt --;break;}}}}}//剪枝3:对于每一列,如果某个字母都不能出现或只能出现一次//只需将剪枝2中i,j互换for(int i = 0; i < N; i ++){int sor = 0, sand = (1 << N) - 1;int drawn = 0;for(int j = 0; j < N; j ++){sand &= ~(state[j][i] & sor);sor |= state[j][i];if(sudu[j][i] != '-') drawn |= state[j][i];}if(sor != (1 << N) - 1)//存在有不能出现的字母{memcpy(sudu, bsudu[kcnt], sizeof sudu);memcpy(state, bstate[kcnt], sizeof state);return false;}for(int k = sand; k; k -= lowbit(k)){int put = lowbit(k);if(!(drawn & put))//未被填过,填{for(int j = 0; j < N; j ++){if(state[j][i] & put){draw(j, i, map[put]);cnt --;break;}}}}}//剪枝4:对于每一块,如果某个字母都不能出现或只能出现一次for(int i = 0; i < N; i += 4)for(int j = 0; j < N; j += 4){int sor = 0, sand = (1 << N) - 1;int drawn = 0;for(int m = 0; m < 4; m ++)for(int n = 0; n < 4; n ++){sand &= ~(state[i + m][j + n] & sor);sor |= state[i + m][j + n];if(sudu[i + m][j + n] != '-') drawn |= state[i + m][j + n];}if(sor != (1 << N) - 1)//存在有不能出现的字母{memcpy(sudu, bsudu[kcnt], sizeof sudu);memcpy(state, bstate[kcnt], sizeof state);return false;}for(int k = sand; k; k -= lowbit(k)){int put = lowbit(k);if(!(drawn & put))//未被填过,填{int flag = 1;for(int m = 0; m < 4 && flag; m++)for(int n = 0; n < 4 && flag; n++){if(state[i + m][j + n] & put){draw(i + m, j + n, map[put]);cnt --;flag = 0;}}}}}//递归出口if(!cnt) return true;//优化:从可填数最少处开始填int minv = 100, x = -1, y = -1;for(int i = 0; i < N; i ++)for(int j = 0; j < N; j ++){if(sudu[i][j] == '-' && ones[state[i][j]] < minv){minv = ones[state[i][j]];x = i, y = j;}}memcpy(bstate2[kcnt], state, sizeof state);for(int i = state[x][y]; i; i -= lowbit(i)){int put = lowbit(i);draw(x, y, map[put]);if(dfs(cnt - 1)) return true;memcpy(state, bstate2[kcnt], sizeof state);}memcpy(sudu, bsudu[kcnt], sizeof sudu);memcpy(state, bstate[kcnt], sizeof state);return false;}void print()
{for(int i = 0; i < N; i ++)cout << sudu[i] << endl;cout << endl;
}void init()
{for(int i = 0; i < N; i++)for(int j = 0; j < N; j++)state[i][j] = (1 << N) - 1;}
int main()
{for(int i = 0; i < N; i++)map[1 << i] = i;for(int i = 0; i < 1 << N; i++)for(int j = i; j; j -= lowbit(j))ones[i] ++;while(cin >> sudu[0]){init();for(int i = 1; i < N; i++)cin >> sudu[i];int cnt = 0;for(int i = 0; i < N; i ++)for(int j = 0; j < N; j ++)if(sudu[i][j] != '-'){draw(i, j, sudu[i][j] - 'A');}else cnt++;dfs(cnt);print();}}

成文 凌晨1:34分,为了那悲催的阅读量,还是放到明天10点发吧

算法:数独专题(2)相关推荐

  1. .[算法]图论专题之最短路径

    .[算法]图论专题之最短路径 作者:jasonkent27 转载请注明出处:www.cnblogs.com/jasonkent27 1. 前言 1.1 最短路引入 小明和小天现在住在海口(C1),他们 ...

  2. 二分法、三分法 --算法竞赛专题解析(1)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 2019.8 网购:京东 当当      作者签名书 如有建议, ...

  3. 2021新年算法小专题—2.股票买卖利润(Java)

    概述 熟悉这类题目的同学都知道,事实上这是一类动态规划(dp)问题,在小专题中都暂且只针对这一类问题进行解决了,更全面的动态规划文章以后有机会再努力吧! 简单介绍一句动态规划.动态规划实际是一种分治思 ...

  4. 2021新年算法小专题—2.股票买卖利润刷题(Java)

    本篇是股票买卖问题的更多题解,在上一篇文章中我们已经介绍了这一题型,实际上是一类dp问题,我们用自动机的思想去解决,在上一篇中我们以一道限定只买卖一次股票的题目为例进行了讲解,文章链接.下面我们继续完 ...

  5. 四边形不等式优化 --算法竞赛专题解析(10)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 2019.8 网购:京东 当当      作者签名书 如有建议, ...

  6. 【算法数据结构专题】「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(下)

    承接上文 承接上一篇文章[算法数据结构专题]「延时队列算法」史上手把手教你针对层级时间轮(TimingWheel)实现延时队列的开发实战落地(上)]我们基本上对层级时间轮算法的基本原理有了一定的认识, ...

  7. 树形DP --算法竞赛专题解析(17)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 网购:京东 当当      想要一本作者签名书?点我 如有建议, ...

  8. 尺取法 --算法竞赛专题解析(2)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 2019.8 网购:京东 当当      作者签名书 如有建议, ...

  9. 并查集算法总结专题训练

    并查集算法总结&专题训练 1.概述 2.模板 3.例题 1.入门题: 2.与别的算法结合: 3.考思维的题: 4.二维转一维: 5.扩展域并查集&边带权并查集: 4.总结 1.概述 并 ...

最新文章

  1. go1.5及以上版本交叉编译
  2. USTC小道消息20220119
  3. 征战蓝桥 —— 2013年第四届 —— C/C++A组第8题——买不到的数目
  4. 【qduoj - 夏季学期创新题】骑士游历(递推dp)
  5. web应用调试工具_如何使用浏览器开发人员工具调试渐进式Web应用程序
  6. 【华为云技术分享】MySQL Seconds_Behind_Master简要分析
  7. 吃掉那只青蛙_每日可交付成果–吃青蛙的艺术
  8. 篮球弹起问题(for循环)
  9. 日访问量1万mysql_日访问量1万服务器
  10. 2021 年“认证杯”网络挑战赛 B 题(第二阶段)
  11. jmeter入门学习,第二篇jmeter采样器sample
  12. RabbitMQ的Queue详解;
  13. 房地产投资占GDP比例畸高 中国房地产泡沫是一颗毒瘤
  14. Monash call (莫纳什来电) -开篇
  15. 计算机pe启动蓝屏怎么办,U盘重装系统进入PE蓝屏怎么办
  16. 分省三农数据超大量面板数据集(1999-2020年)
  17. 深信服——字符串模糊匹配
  18. meb备份mysql,MySQL企业版备份工具MEB
  19. 深入学习ElasticSearch(一)——ElasticSearch安装
  20. 常用CORS账号设置方法(全国(千寻)CORS、中国移动CORS、华测CORS、北斗CORS)

热门文章

  1. Linux编码及dos2unix,unix2dos命令
  2. python 医学图像膝关节数据raw和mhd转换为png,jpg,bmp和tif格式, plt.savefig去白边, 坐标值和轴
  3. js判断字符串包含某个字符串的几种方法
  4. mysql数据库中图片的类型_将Image类型的图片文件保存到Mysql数据库
  5. Dubbo详解(一)分布式服务框架的概念理解
  6. java xmx设置_JVM内存设置多大合适?Xmx和Xmn如何设置?
  7. 微信小程序的人工智能模型部署(flask)
  8. 编程如何在墙上开圆形洞口
  9. thinkphp 5.0 php7.2,thinkphp5.0生命周期
  10. 很值得一读精彩语句 转自@红薯