[NOIP2009 提高组] 靶形数独
题目背景
此为远古题,不保证存在可以通过任意符合要求的输入数据的程序。
题目描述
小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 9 9 格宽且 9 9 9 格高的大九宫格中有 9 9 9 个 3 3 3 格宽且 3 3 3 格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 1 1 到 9 9 9 的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)
上图具体的分值分布是:最里面一格(黄色区域)为 10 10 10 分,黄色区域外面的一圈(红色区域)每个格子为 9 9 9 分,再外面一圈(蓝色区域)每个格子为 8 8 8 分,蓝色区域外面一圈(棕色区域)每个格子为 7 7 7 分,最外面一圈(白色区域)每个格子为 6 6 6 分,如上图所示。比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和
总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829 2829 2829。游戏规定,将以总分数的高低决出胜负。
由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。
输入格式
一共 9 9 9 行。每行 9 9 9 个整数(每个数都在 0 ∼ 9 0 \sim 9 0∼9 的范围内),表示一个尚未填满的数独方格,未填的空格用“ 0 0 0”表示。每两个数字之间用一个空格隔开。
输出格式
输出共 1 1 1 行。输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数 − 1 -1 −1。
样例 #1
样例输入 #1
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
样例输出 #1
2829
样例 #2
样例输入 #2
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
样例输出 #2
2852
提示
数据规模与约定
- 对于 40 % 40\% 40% 的数据,数独中非 0 0 0 数的个数不少于 30 30 30;
- 对于 80 % 80\% 80% 的数据,数独中非 0 0 0 数的个数不少于 26 26 26;
- 对于 100 % 100\% 100% 的数据,数独中非 0 0 0 数的个数不少于 24 24 24。
题目来源
NOIP 2009 提高组 第三题
#include<iostream>
#include<algorithm>
using namespace std;
// 定义结构体f,用于存储每行信息,包括行号和该行中0的个数
struct f {
int rank, sum;
} cou[10];
int a[10][10], hang[10][10], lie[10][10], gong[10][10], s[100][4], u, ok, most = -1, have;
// 函数声明
int which(int, int); // 返回给定坐标所在的小九宫格编号
int point(int, int); // 返回给定坐标的分值
void dfs(int, int); // 深度优先搜索函数
// 自定义比较函数,用于排序
bool cmp(f a, f b) {
return a.sum < b.sum;
}
int main() {
// 初始化rank
for (int i = 1; i <= 9; i++) {
cou[i].rank = i;
}
// 输入数独并初始化
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
cin >> a[i][j];
if (a[i][j] > 0) {
// 标记已知数字的位置
hang[i][a[i][j]] = lie[j][a[i][j]] = gong[which(i, j)][a[i][j]] = 1;
have += a[i][j] * point(i, j); // 计算已有数字的总分
} else {
cou[i].sum++; // 统计每行中0的个数
}
}
}
// 按0的个数对行进行排序
sort(cou + 1, cou + 10, cmp);
// 整理s数组,准备进行搜索
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
if (a[cou[i].rank][j] == 0) {
s[u][0] = cou[i].rank;
s[u][1] = j;
s[u][2] = point(cou[i].rank, j);
s[u++][3] = which(cou[i].rank, j);
}
}
}
// 进行深度优先搜索
dfs(0, have);
// 输出最高得分
cout << most << endl;
return 0;
}
// 深度优先搜索函数
void dfs(int p, int score) {
if (p == u) {
// 如果已经填完所有的数,更新最高分数
if (score > most) {
most = score;
}
return;
}
for (int i = 1; i <= 9; i++) {
// 检查能否填入数字i
if (!hang[s[p][0]][i] && !lie[s[p][1]][i] && !gong[s[p][3]][i]) {
// 标记填入的数字
hang[s[p][0]][i] = lie[s[p][1]][i] = gong[s[p][3]][i] = 1;
// 递归搜索下一步
dfs(p + 1, score + (s[p][2] * i));
// 回溯,撤销标记
hang[s[p][0]][i] = lie[s[p][1]][i] = gong[s[p][3]][i] = 0;
}
}
}
// 返回小九宫格的编号
int which(int i, int j) {
if (i <= 3) {
if (j <= 3) return 1;
else if (j <= 6) return 2;
else return 3;
} else if (i <= 6) {
if (j <= 3) return 4;
else if (j <= 6) return 5;
else return 6;
} else {
if (j <= 3) return 7;
else if (j <= 6) return 8;
else return 9;
}
}
// 返回坐标的分值
int point(int i, int j) {
if (i == 1 || j == 1 || i == 9 || j == 9) return 6;
if (i == 2 || j == 2 || i == 8 || j == 8) return 7;
if (i == 3 || j == 3 || i == 7 || j == 7) return 8;
if (i == 4 || j == 4 || i == 6 || j == 6) return 9;
return 10;
}