DeepSeek撰写NOIP2009T4靶形数独解题报告

目录

NOIP2009提高组DeepSeek题解系列

一、提问词

二、DeepSeek回复

题解:NOIP2009提高组T4 靶形数独

题目描述

解题思路

实现代码

代码说明

三、说明


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。

得分计算方式:每个位置的得分乘以填入的数字,所有位置的得分之和为最终结果。

解题思路
  1. 回溯算法与剪枝:采用深度优先搜索(DFS)回溯尝试所有可能的填数方式,并通过剪枝减少无效搜索。

  2. 优化搜索顺序:每次选择候选数字最少的空格进行填充,减少递归分支。

  3. 位运算优化:使用位掩码快速记录每行、每列、每个九宫格的已填数字,加速状态检查。

  4. 剪枝策略:若当前得分加上剩余空格的最大可能得分无法超过已有最大值,则提前终止该分支的搜索。

实现代码
#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的个数数组。

  2. 输入处理:读取数独,初始化掩码和初始得分,收集未填位置的分数。

  3. 深度优先搜索:动态选择候选最少的空格,尝试所有可能的数字,递归搜索并剪枝。

  4. 剪枝优化:通过前缀和数组预估剩余可能得分,提前终止无效分支。

  5. 结果输出:写入最大得分,若未找到解输出-1。

该代码通过位运算和剪枝策略高效解决问题,确保在合理时间内找到最优解。 

三、说明

1、基于【Cena】和【NOI Linux 2.0】测试数据和参考程序:https://download.csdn.net/download/liufangshun/90460872。

2、本解题报告没有使用【IMA+DeepSeek+个人知识库】撰写的原因,是用这个环境写了三四次,要么推理过程无法完成,要么提供的代码不能通过测评。

3、提问词中加了一个测试数据的原因,DeepSeek第一次撰写的题解,有17、20这两个数据超时,提问词中提供的就是17号测试数据的输入数据。增加这个提问要求后再测就全部通过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liufangshun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值