目录
NOIP2009提高组DeepSeek题解系列
T1:IMA+DeepSeek撰写NOIP2009提高组T1潜伏者解题报告-CSDN博客
T2:IMA+DeepSeek撰写NOIP2009T2Hankson 的趣味题解题报告-CSDN博客
T3:IMA+DeepSeek撰写NOIP2009T3最优贸易解题报告-CSDN博客
T4:DeepSeek撰写NOIP2009T4靶形数独解题报告-CSDN博客
基于【Cena】和【NOI Linux 2.0】测试数据和参考程序:https://download.csdn.net/download/liufangshun/90460872。按竞赛要求完成了”NOI Linux 2.0“环境使用【Arbiter】、【Windows】环境使用【Cena】评测的试题设置,官方标准数据也放到相应位置,提供学生测试样例作参考。
一、提问词
请撰写NOIP2009提高组T4靶形数独题解,要求:
一、从文件“sudoku.in”读数据,答案写入文件“sudoku.out”。
二、题解至少包括以下几个部分:
1、题目描述
2、解题思路及推理过程
3、实现代码
4、代码说明
三、代码符合C++98规范的要求,且严格遵循文件操作规范。
四、对于下面输入数据符合时间要求
'''
0 0 0 9 0 0 0 0 0
7 0 0 2 0 0 0 0 0
5 2 0 8 0 0 0 0 0
6 0 0 7 0 2 0 3 4
2 0 0 0 0 0 0 0 7
3 7 4 0 0 0 0 0 5
9 0 2 4 1 0 0 7 0
0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 9 0 0
'''
二、DeepSeek回复
题解:NOIP2009提高组T4 靶形数独
题目描述
靶形数独的规则与普通数独相同,但每个格子的得分不同,形成一个靶形结构。中心区域得分最高,向外逐层递减。玩家需要将数字1-9填入空白格(用0表示),使得每行、每列和每个3×3的九宫格中的数字都不重复。求所有合法填法中,得分总和的最大值。若没有解,输出-1。
得分计算方式:每个位置的得分乘以填入的数字,所有位置的得分之和为最终结果。
解题思路
-
回溯算法与剪枝:采用深度优先搜索(DFS)回溯尝试所有可能的填数方式,并通过剪枝减少无效搜索。
-
优化搜索顺序:每次选择候选数字最少的空格进行填充,减少递归分支。
-
位运算优化:使用位掩码快速记录每行、每列、每个九宫格的已填数字,加速状态检查。
-
剪枝策略:若当前得分加上剩余空格的最大可能得分无法超过已有最大值,则提前终止该分支的搜索。
实现代码
#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
using namespace std;
int score[9][9]; // 每个位置的分数
int ones[1<<9]; // 预处理每个数的二进制中1的个数
int sudoku[9][9]; // 数独当前状态
int row[9], col[9], block[9]; // 行、列、九宫格的掩码
int max_score = -1; // 最大得分
int empty_cells; // 剩余未填的空格数
vector<int> sorted_scores; // 未填位置的分数降序排列
int prefix[82]; // 前缀和数组,用于剪枝
// 初始化每个位置的分数
void init_score() {
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
int d = max(abs(i - 4), abs(j - 4));
score[i][j] = 10 - d;
}
}
}
// 预处理二进制中1的个数
void init_ones() {
for (int i = 0; i < (1<<9); ++i) {
int cnt = 0;
int tmp = i;
while (tmp) {
tmp &= tmp - 1;
cnt++;
}
ones[i] = cnt;
}
}
void dfs(int current_sum) {
// 剪枝:当前得分加上剩余可能最大值仍不够
if (current_sum + prefix[empty_cells] <= max_score) {
return;
}
// 寻找候选数最少的空格
int min_count = 10, x = -1, y = -1;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (sudoku[i][j] == 0) {
int b = (i / 3) * 3 + (j / 3);
int mask = row[i] | col[j] | block[b];
int available = (~mask) & 0x1ff;
int cnt = ones[available];
if (cnt == 0) return; // 存在无法填的空格
if (cnt < min_count) {
min_count = cnt;
x = i;
y = j;
}
}
}
}
if (x == -1) { // 找到解,更新最大值
max_score = max(max_score, current_sum);
return;
}
int b = (x / 3) * 3 + (y / 3);
int mask = row[x] | col[y] | block[b];
int available = (~mask) & 0x1ff;
// 尝试所有可填的数字
for (int num = 1; num <= 9; ++num) {
if (available & (1 << (num - 1))) {
// 更新状态
sudoku[x][y] = num;
row[x] |= (1 << (num - 1));
col[y] |= (1 << (num - 1));
block[b] |= (1 << (num - 1));
int add = score[x][y] * num;
empty_cells--;
dfs(current_sum + add);
empty_cells++;
// 回溯
sudoku[x][y] = 0;
row[x] &= ~(1 << (num - 1));
col[y] &= ~(1 << (num - 1));
block[b] &= ~(1 << (num - 1));
}
}
}
int main() {
init_ones();
init_score();
ifstream fin("sudoku.in");
ofstream fout("sudoku.out");
int initial_sum = 0;
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
fin >> sudoku[i][j];
int num = sudoku[i][j];
if (num != 0) {
row[i] |= 1 << (num - 1);
col[j] |= 1 << (num - 1);
int b = (i / 3) * 3 + (j / 3);
block[b] |= 1 << (num - 1);
initial_sum += score[i][j] * num;
} else {
sorted_scores.push_back(score[i][j]);
}
}
}
empty_cells = sorted_scores.size();
sort(sorted_scores.begin(), sorted_scores.end(), greater<int>());
prefix[0] = 0;
for (int i = 1; i <= empty_cells; ++i) {
prefix[i] = prefix[i - 1] + sorted_scores[i - 1] * 9;
}
dfs(initial_sum);
fout << max_score << endl;
fin.close();
fout.close();
return 0;
}
代码说明
-
预处理:初始化分数数组和二进制1的个数数组。
-
输入处理:读取数独,初始化掩码和初始得分,收集未填位置的分数。
-
深度优先搜索:动态选择候选最少的空格,尝试所有可能的数字,递归搜索并剪枝。
-
剪枝优化:通过前缀和数组预估剩余可能得分,提前终止无效分支。
-
结果输出:写入最大得分,若未找到解输出-1。
该代码通过位运算和剪枝策略高效解决问题,确保在合理时间内找到最优解。
三、说明
1、基于【Cena】和【NOI Linux 2.0】测试数据和参考程序:https://download.csdn.net/download/liufangshun/90460872。
2、本解题报告没有使用【IMA+DeepSeek+个人知识库】撰写的原因,是用这个环境写了三四次,要么推理过程无法完成,要么提供的代码不能通过测评。
3、提问词中加了一个测试数据的原因,DeepSeek第一次撰写的题解,有17、20这两个数据超时,提问词中提供的就是17号测试数据的输入数据。增加这个提问要求后再测就全部通过。
322

被折叠的 条评论
为什么被折叠?



