计蒜客上的一道深搜题。
从深搜的角度说,没有什么花样,就是数独,DFS,判断是不是用过了这个数字,然后放满所有的格子以后计算得分,取最大得分。
这道题我觉得他很好,是因为有很多剪枝的地方,稍不注意就会超时。
原题如下:
蒜头君天资聪颖,酷爱数学,尤其擅长做数独游戏。不过普通的数独游戏已经满足不了蒜头君了,于是他发明了一种“金字塔数独”:下图即为金字塔数独。和普通数独一样,在 9×9 的大九宫格中有 9 个 3×3 的小九宫格(用粗黑色线隔开的)。要求每个格子上都有一个 1 到 9 的数字,每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但金字塔数独的每一个格子都有一个分值,类似金字塔的俯视图。如图所示,白色格子为 6 分,蓝色格子为 7 分,绿色格子为 8 分,紫色格子为 9 分,红色格子为 10 分。颜色相同的格子分值一样,离中心越近则分值越高。

金字塔数独的总分等于每个格子上的数字和对应的分值乘积之和。现在蒜头君给定金字塔数独的若干数字,请问如何填写,可以使得金字塔数独的总分最高。
输入格式:输入一共 9 行。每行输入 9 个整数(每个数都在 0—9 的范围内),每两个整数之间用一个空格隔开,“0”表示该格子为空。
输出格式:输出为一行,输出一个整数,代表金字塔数独的最高总分。如果数独无解,则输出 −1。
#include <stdio.h>
#include <iostream>
#define WIDTH 3
#define BOARD_SIZE 9
#define CANDIDATES_RANGE 9
const int SCORE_WEIGHT[BOARD_SIZE][BOARD_SIZE] = {
{6, 6, 6, 6, 6, 6, 6, 6, 6},
{6, 7, 7, 7, 7, 7, 7, 7, 6},
{6, 7, 8, 8, 8, 8, 8, 7, 6},
{6, 7, 8, 9, 9, 9, 8, 7, 6},
{6, 7, 8, 9, 10, 9, 8, 7, 6},
{6, 7, 8, 9, 9, 9, 8, 7, 6},
{6, 7, 8, 8, 8, 8, 8, 7, 6},
{6, 7, 7, 7, 7, 7, 7, 7, 6},
{6, 6, 6, 6, 6, 6, 6, 6, 6}
};
int board[BOARD_SIZE][BOARD_SIZE] = {{0}};
int ans = -1;
// 暴力搜索会超时 case 5,改成标记
//int isValid(int row, int col, int val) {
// for (int i = 0; i < BOARD_SIZE; i++)
// if (i != row && board[i][col] == val)
// return false;
// for (int j = 0; j < BOARD_SIZE; j++)
// if (j != col && board[row][j] == val)
// return false;
// int majorRow = row / WIDTH;
// int majorCol = col / WIDTH;
// for (int i = majorRow * WIDTH; i < majorRow * WIDTH + WIDTH; i++)
// for (int j = majorCol * WIDTH; j < majorCol * WIDTH + WIDTH; j++)
// if (i != row && j != col && board[i][j] == val)
// return false;
// return true;
//}
// 以 rowUsed 为例,rowUsed[i][j] == true 表示第 i 行的数字 j 已经出现过
bool rowUsed[BOARD_SIZE][CANDIDATES_RANGE + 1] = {{0}};
bool colUsed[BOARD_SIZE][CANDIDATES_RANGE + 1] = {{0}};
bool gridUsed[BOARD_SIZE][CANDIDATES_RANGE + 1] = {{0}};
bool isValid(int row, int col, int val) {
return (!rowUsed[row][val] && !colUsed[col][val] && !gridUsed[row / WIDTH * WIDTH + col / WIDTH][val]);
}
void setNumber(int row, int col, int val, bool status) {
board[row][col] = status ? val : 0;
colUsed[col][val] = status;
rowUsed[row][val] = status;
gridUsed[row / WIDTH * WIDTH + col / WIDTH][val] = status;
}
int calculateScore() {
int score = 0;
for (int i = 0; i < BOARD_SIZE; i++)
for (int j = 0; j < BOARD_SIZE; j++)
score += board[i][j] * SCORE_WEIGHT[i][j];
return score;
}
void dfs(int depth, int candidatesNumber) {
if (depth == candidatesNumber) {
ans = std::max(calculateScore(), ans);
return;
}
/*
* 这里有个 trick,可能要看了题解(数据)才知道了
* i 从 0 到 9,j 从 0 到 9,case 3 和 case 4 会超时,从 9 反过来回到 0 就不会
* 本质上没有区别,只是数据恶心了些
*/
for (int i = BOARD_SIZE - 1; i >= 0; i--)
for (int j = BOARD_SIZE - 1; j >= 0; j--) {
if (board[i][j] != 0)
continue;
for (int candidate = 1; candidate <= CANDIDATES_RANGE; candidate++) {
if (isValid(i, j, candidate)) {
setNumber(i, j, candidate, true);
dfs(depth + 1, candidatesNumber);
setNumber(i, j, candidate, false);
}
}
return; // 这个 return 必须要剪枝,9个数都放完了就 return,否则会很慢很慢很慢,样例都过不了
}
}
int main() {
int candidatesNumber = 0;
for (int i = 0; i < BOARD_SIZE; i++)
for (int j = 0; j < BOARD_SIZE; j++) {
int x;
scanf("%d", &x);
setNumber(i, j, x, true);
if (x == 0)
candidatesNumber++;
}
dfs(0, candidatesNumber);
printf("%d", ans);
return 0;
}
