蒜头君天资聪颖,酷爱数学,尤其擅长做数独游戏。不过普通的数独游戏已经满足不了蒜头君了,于是他发明了一种“金字塔数独”:
下图即为金字塔数独。和普通数独一样,在9 * 9的大九宫格中有 9个3 * 3的小九宫格(用粗黑色线隔开的)。要求每个格子上都有一个 1 到 9 的数字,每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。
但金字塔数独的每一个格子都有一个分值,类似金字塔的俯视图。如图所示,白色格子为 6 分,蓝色格子为 7 分,绿色格子为 8 分,紫色格子为 9 分,红色格子为 10 分。颜色相同的格子分值一样,离中心越近则分值越高。
金字塔数独的总分等于每个格子上的数字和对应的分值乘积之和。现在蒜头君给定金字塔数独的若干数字,请问如何填写,可以使得金字塔数独的总分最高。
输入一共9行。每行输入9个整数(每个数都在0-9 的范围内),每两个整数之间用一个空格隔开,“0”表示该格子为空。
输出为一行,输出一个整数,代表金字塔数独的最高总分。如果数独无解,则输出-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
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;
}