dfs+剪枝求解靶形数独(二进制优化)
题目描述
蒜头君天资聪颖,酷爱数学,尤其擅长做数独游戏。不过普通的数独游戏已经满足不了蒜头君了,于是他发明了一种“金字塔数独”:
下图即为金字塔数独。和普通数独一样,在9 * 9的大九宫格中有9个3 * 3的小九宫格(用粗黑色线隔开的)。要求每个格子上都有一个 1 到 9 的数字,每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。
但金字塔数独的每一个格子都有一个分值,类似金字塔的俯视图。如图所示,白色格子为 6 分,蓝色格子为 7 分,绿色格子为 8 分,紫色格子为 9 分,红色格子为 10 分。颜色相同的格子分值一样,离中心越近则分值越高。
金字塔数独的总分等于每个格子上的数字和对应的分值乘积之和。现在蒜头君给定金字塔数独的若干数字,请问如何填写,可以使得金字塔数独的总分最高。
Input
输入一共9行。每行输入9个整数(每个数都在0-9 的范围内),每两个整数之间用一个空格隔开,“0”表示该格子为空。
Output
输出为一行,输出一个整数,代表金字塔数独的最高总分。如果数独无解,则输出-1。
样例
Input
0 0 0 0 0 0 0 0 0
0 0 3 0 0 0 9 0 0
7 9 0 0 0 2 0 1 3
0 0 9 1 5 0 0 0 0
0 7 4 0 2 6 1 3 9
0 0 6 0 0 0 0 0 0
6 0 0 0 0 7 0 0 0
3 1 0 4 0 5 7 9 6
0 0 7 0 0 1 0 4 0
Output
2864
题目分析
数独类型的题目都是典型的用dfs来解,但这题单纯用dfs会TLE,我也是看了b站上一位大佬的视频讲解才懂了。这题要用二进制的方法来优化。用两个一维数组分别储存每一行和每一列的填数情况,再用一个二维数组分别储存每一宫的填数情况。
例如:100111111表示7和8(从左往右第7位和第8位为0)还未填,其他数已填。
然后开两个一维数组作"小抄",ones用来储存1到(1<<9)(即2^8)中每个数字(二进制)含多少个1,map用来储存一个数(二进制)为1的最高位是第几位。
然后就是lowbit运算,用以返回x在二进制表示下最低位的1以及它后面的0构成的数值。原理就不过多解释了。
例如:110010100经过运算后得到100。
将lowbit与map数组搭配就可以得到一个位置的可填数字了
#include<iostream> using namespace std; int a[10][10], row[9], col[9], cell[3][3]; int ones[1 << 9], map[1 << 9], Max = 0; int score(int x, int y) //每一圈所得分数 { if(x == 0 || x == 8 || y == 0 || y == 8) return 6; if((x > 0 && x < 8 && (y == 1 || y == 7)) || (y > 0 && y < 8 && (x == 1 || x == 7))) return 7; if((x > 1 && x < 7 && (y == 2 || y == 6)) || (y > 1 && y < 7 && (x == 2 || x == 6))) return 8; if((x > 2 && x < 6 && (y == 3 || y == 5)) || (y > 2 && y < 6 && (x == 3 || x == 5))) return 9; if(x == 4 && y == 4) return 10; } int intersection(int x, int y) //取该点可填数字的交集 { return row[x] & col[y] & cell[x / 3][y / 3]; } int lowbit(int x) // 返回x在二进制表示下最低位的1以及它后面的0构成的数值 { return x & -x; } void dfs(int step) { int i, j, x, y, Min = 10; if(!step) //全部填完后记下所得分数,然后回溯 { int sum = 0; for(int k = 0; k < 9; k++) { for(int l = 0; l < 9; l++) { sum += a[k][l] * score(k, l); } } Max = max(Max, sum); return; } for(i = 0; i < 9; i++) //寻找可填数字最少的位置 ,从该位置开始深搜 for(j = 0; j < 9; j++) if(!a[i][j]) { int sum = ones[intersection(i, j)]; if(sum < Min) { Min = sum; x = i; y = j; } } for(i = intersection(x, y); i > 0; i -= lowbit(i)) { int s = map[lowbit(i)]; a[x][y] = s + 1; //加上一是因为map数组储存的是0到8,而要填的数字是1到9,因此要把0~8映射成1~9 row[x] -= 1 << s; col[y] -= 1 << s; cell[x / 3][y / 3] -= 1 << s; //填上一个数后将该位标记为0 dfs(step - 1); a[x][y] = 0; row[x] += 1 << s; col[y] += 1 << s; cell[x / 3][y / 3] += 1 << s; //还原 } } void init() //初始化 { int i, j; for(i = 0; i < 9; i++) { row[i] = (1 << 9) - 1; col[i] = (1 << 9) - 1; //记得加括号 ,<<优先级低于-。 } for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) cell[i][j] = (1 << 9) - 1; //将每一行每一列每一宫都初始化为111111111,表示每个数字都可使用 for(i = 0; i < 1 << 9; i++) { int sum = 0; for(j = i; j > 0; j -= lowbit(j)) sum++; ones[i] = sum; } //ones数组用来储存1到(1<<9)(即2^8)中每个数字(二进制)含多少个1 for(i = 0; i < 9; i++) map[1 << i] = i; //map数组用来储存一个数(二进制)为1的最高位是第几位。 } int main() { int i, j, count = 0; for(i = 0; i < 9; i++) for(j = 0; j < 9; j++) cin >> a[i][j]; for(i = 0; i < 9; i++) for(j = 0; j < 9; j++) { if(a[i][j]) //将已填数字所在行,列,宫的该位标记为0 { int k = a[i][j] - 1;//同理,把1~9映射成0~8 row[i] -= 1 << k; col[j] -= 1 << k; cell[i / 3][j / 3] -= 1 << k; } else count++; //记下还有多少位置未填 } dfs(count); if(!Max) cout << "-1"; else cout << Max; return 0; }