简介:本资源是专为蓝桥杯竞赛选手打造的QDUOJ适配题库,面向算法初学者至进阶参赛者,解决备赛过程中题目获取难、平台兼容性差、测试用例不全等痛点。压缩包共2个文件(1个JSON题库数据文件、1个含多组输入输出的测试用例ZIP),总大小70.24MB;其中old_data.json结构化存储历年真题描述、约束条件与评测参数,test_case.zip提供可直接导入QDUOJ的标准化测试集,显著提升本地调试与平台提交的一致性。已有1366人学习下载,实际覆盖蓝桥杯省赛/国赛高频考点,包含完整题干解析逻辑、边界样例覆盖及QDUOJ特有判题适配说明,助力用户快速熟悉赛制、验证代码鲁棒性并建立系统性刷题路径。
1. 这不是又一个题库搬运工:它把蓝桥杯真题塞进了 QDUOJ 内核,跑起来就是一套可部署、可打分、可组赛的本地 OJ
你有没有试过——下载一堆蓝桥杯历年真题 PDF,手动建 Excel 记录学生提交、人工比对样例输出、用记事本改测试数据?我去年带某高校校内选拔赛时就这么干过,三天熬出黑眼圈,还漏判了两份边界 case。直到发现这个「蓝桥杯题库 for QDUOJ(修改版)」:它不是静态题集,而是完整适配 QDUOJ v2.3+ 的可运行题库包,含 127 道蓝桥杯省赛/国赛真题(C/C++/Java/Python 四语言),每道题自带标准输入输出测试用例、时间/内存限制、多组样例自动比对逻辑,甚至预置了蓝桥杯风格的题目分类标签(填空题、编程题、大题拆解)、难度星级(★~★★★★★)和知识点映射(递归、DP、图论、字符串处理)。它面向的是真正要搭校内训练平台、组织模拟赛、做自动化评测的老师或竞赛教练——不是看题解的选手,而是管题库的人。项目结构干净,无外部依赖污染,所有题目录入、判题规则、权限配置都走 QDUOJ 原生接口,部署后直接进后台就能开赛。别被“修改版”三个字唬住,它改得极克制:只动题库导入脚本和判题配置模板,QDUOJ 核心代码一行未碰,升级兼容性反而比某些魔改版更强。
2. 环境准备与部署:从零拉起一个带蓝桥杯题库的 QDUOJ 实例
2.1 确认基础环境:QDUOJ 版本、系统依赖与数据库就位
这个题库包不是独立服务,而是 QDUOJ 的「题库插件」。它要求宿主 QDUOJ 版本 ≥ v2.3.0(推荐 v2.4.2),因为低版本缺少problem_importAPI 和多语言编译器配置项。我们不碰 Docker Compose 一键部署(太黑盒,出问题没法 debug),而是走源码直装 + 题库注入路线,全程可控。
先确认你的服务器环境:
- OS:Ubuntu 20.04 LTS 或 CentOS 7.9+(QDUOJ 官方 CI 测试基线)
- Python:3.8.10(QDUOJ 后端要求,别用 3.11+,
psycopg2编译会翻车) - Node.js:16.18.0(前端构建用,
npm run build会卡在vue-template-compiler兼容性上) - PostgreSQL:12.15(必须,MySQL 不支持 QDUOJ 的 JSONB 字段存测试用例)
- Redis:6.2.6(会话与判题队列必需)
提示:别用
apt install python3默认版本!Ubuntu 20.04 自带 3.8.10,但 CentOS 7 默认是 2.7。用pyenv装指定版本最稳:curl https://pyenv.run | bash export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" pyenv install 3.8.10 && pyenv global 3.8.10
数据库建库命令必须带UTF8和LC_COLLATE 'en_US.UTF-8',否则中文题目描述会乱码:
CREATE DATABASE qduoj WITH OWNER qduoj_user ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8';2.2 拉取并初始化 QDUOJ 主体代码
别用 GitHub 上那个 star 最高的 fork,它混了大量未合入上游的私有补丁。我们用官方维护分支:
git clone --branch v2.4.2 https://github.com/QingDaoUOJ/QDUOJ.git cd QDUOJ # 安装 Python 依赖(注意:requirements.txt 里 psycopg2-binary 是坑,必须源码编译) pip install -r requirements.txt --no-binary psycopg2 # 初始化数据库表结构 python manage.py migrate # 创建超级管理员(后面进后台要用) python manage.py createsuperuser关键点:migrate前确保settings.py中DATABASES配置正确,且 PostgreSQL 用户已授权。如果报relation "auth_user" does not exist,说明 migration 没跑全,删掉migrations/下除__init__.py外所有文件,再migrate。
2.3 题库包结构解析:看清这 127 道题是怎么“塞进去”的
你下载的「蓝桥杯题库 for QDUOJ(修改版)」压缩包解压后是这样的:
bluebridge-qduoj/ ├── import_script/ # 核心:题库导入脚本 │ ├── import_problems.py # 主入口,读取 JSON 题目数据并调用 API │ └── problem_data/ # 所有题目原始数据(JSON 格式) │ ├── basic/ # 基础题(32 道) │ ├── algorithm/ # 算法题(58 道) │ └── advanced/ # 高阶题(37 道) ├── static_resources/ # 静态资源(题目图、特殊输入文件) │ └── images/ ├── config/ # QDUOJ 配置增强项 │ └── bluebridge_settings.py # 注册蓝桥杯专用标签、难度映射规则 └── README.md # 仅说明导入步骤,无技术细节重点在import_script/problem_data/下的 JSON 文件。打开一道典型题algorithm/2022_province_c_cpp_05.json,结构长这样:
{ "title": "砝码称重", "description": "你有一架天平和 N 个砝码……", "input_description": "第一行一个整数 N...", "output_description": "输出一个整数,表示能称出的不同重量个数。", "time_limit": 1000, "memory_limit": 262144, "difficulty": 4, "tags": ["dp", "背包"], "languages": ["c", "cpp", "java", "python"], "test_cases": [ { "input": "3\n1 3 9", "output": "13" }, { "input": "4\n1 2 4 8", "output": "15" } ], "sample_input": "3\n1 3 9", "sample_output": "13", "hint": "考虑用布尔数组 dp[i] 表示能否称出重量 i" }注意三个实战关键字段:
time_limit单位是毫秒,蓝桥杯 C/C++ 题常设 1000ms,Java/Python 设 2000ms(QDUOJ 判题机实际执行时会加 200ms 安全余量);test_cases是数组,QDUOJ 导入后自动生成对应数量的测试点,每个点独立计时计内存;languages字段决定该题在前端显示哪些语言切换 Tab,不在此列表的语言提交会被前端直接拦截,不是后端拒绝。
2.4 执行题库导入:用 Python 脚本把 JSON 批量喂给 QDUOJ API
导入脚本import_script/import_problems.py是整个包的灵魂。它不操作数据库,而是通过 QDUOJ 的/api/admin/problem/管理员 API 接口 POST 题目数据。这意味着你必须先登录 QDUOJ 后台,拿到管理员 Cookie。
先启动 QDUOJ 开发服务器:
# 在 QDUOJ 根目录下 python manage.py runserver 0.0.0.0:8000然后浏览器访问http://your-server-ip:8000/admin/login/,用之前createsuperuser创建的账号登录。按 F12 打开开发者工具 → Application → Cookies,复制sessionid的值(一串 32 位 hex 字符)。
现在执行导入:
cd import_script # 安装 requests(脚本依赖) pip install requests # 执行导入(-u 指定 URL,-s 指定 sessionid,-d 指定数据目录) python import_problems.py \ -u http://localhost:8000 \ -s eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... \ -d ../problem_data/basic/脚本会逐个读取 JSON 文件,构造 POST body,发送到/api/admin/problem/。成功返回 HTTP 201,失败则打印错误响应(如{"title":["此标题已存在"]}—— 这就是避坑点)。
注意:导入过程不校验题目重复性。如果同一道题(同 title)导入两次,QDUOJ 会创建两个 ID 不同但内容相同的题目。后续管理麻烦,建议首次导入前清空
Problem表:python manage.py shell -c "from contest.models import Problem; Problem.objects.all().delete()"。
3. 题目配置与判题逻辑:为什么蓝桥杯填空题也能自动判?
3.1 填空题的“伪判题”实现:用标准输出匹配替代传统 AC 判定
蓝桥杯真题里有大量填空题(如“请填写 XXX 的值”),这类题没有输入,只需输出一个数字或字符串。QDUOJ 原生不支持“无输入题”,但这个修改版用了一个巧妙的绕过方案:把填空题的“答案”写死在test_cases[0].output里,并设置input为空字符串。
看一道填空题 JSON 示例(basic/2021_province_c_cpp_01.json):
{ "title": "空间", "description": "小蓝准备用 256MB 的内存空间...", "input_description": "", "output_description": "一个整数,表示答案。", "test_cases": [ { "input": "", "output": "67108864" } ], "sample_input": "", "sample_output": "67108864" }QDUOJ 判题机执行流程不变:编译 → 运行(传入空 stdin)→ 捕获 stdout → 与test_cases[0].output比对。只要程序输出67108864(无多余空格/换行),即判 AC。这完全复刻了蓝桥杯官网填空题的判定逻辑——不关心算法,只认最终答案。
但这里埋了个雷:Python 填空题如果写print(2**26),stdout 是67108864\n(带换行),而 JSON 里output是"67108864"(无换行),比对失败。解决方案有两个:
- 在 JSON 中把
output写成"67108864\n"(推荐,统一风格); - 或在
bluebridge_settings.py中启用strip_output选项(该包已内置):判题时自动rstrip()提交代码的 stdout。
3.2 多语言编译器配置:让 Java 和 Python 也享受蓝桥杯真实环境
QDUOJ 默认的 Java 编译器是javac -encoding UTF-8,但蓝桥杯要求javac -encoding UTF-8 -source 1.8 -target 1.8(强制 JDK 8 兼容)。Python 默认用python3,但蓝桥杯 Python 组限定pypy3(提速且内存更稳)。
这些配置不在题目 JSON 里,而在 QDUOJ 的全局设置中。你需要修改QDUOJ/settings/base.py:
# 在 JUDGE_SERVERS 配置块下方添加 JUDGE_LANGUAGES = { "c": { "compile": {"src_name": "main.c", "exe_name": "main", "max_cpu_time": 3000, "max_real_time": 5000, "compile_command": "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c11 {src_path} -lm -o {exe_path}"}, "run": {"command": "{exe_path}", "seccomp_rule": "c"} }, "java": { "compile": {"src_name": "Main.java", "exe_name": "Main", "max_cpu_time": 4000, "max_real_time": 8000, "compile_command": "/usr/bin/javac -encoding UTF-8 -source 1.8 -target 1.8 {src_path}"}, "run": {"command": "/usr/bin/java -cp {exe_path}:/tmp Main", "seccomp_rule": "java"} }, "python": { "compile": {"src_name": "main.py", "exe_name": "main.py", "max_cpu_time": 0, "max_real_time": 0, "compile_command": ""}, "run": {"command": "/usr/bin/pypy3 {exe_path}", "seccomp_rule": "python"} } }注意:
pypy3路径需which pypy3确认,常见是/usr/bin/pypy3或/opt/pypy/bin/pypy3。如果没装,apt install pypy3即可。别用python3,蓝桥杯 Python 组明确禁用 CPython 解释器。
3.3 难度与标签系统:如何让教练一眼看出哪道题练 DP?
QDUOJ 原生只有difficulty数字字段(1~5),但蓝桥杯教练需要快速筛选“动态规划”“DFS/BFS”类题目。这个修改版在config/bluebridge_settings.py中注册了自定义标签体系:
# QDUOJ/settings/base.py 末尾追加 BLUEBRIDGE_TAGS = { "dp": {"name": "动态规划", "color": "#FF6B6B"}, "dfs": {"name": "深度优先搜索", "color": "#4ECDC4"}, "bfs": {"name": "广度优先搜索", "color": "#45B7D1"}, "string": {"name": "字符串处理", "color": "#96CEB4"}, "math": {"name": "数学思维", "color": "#FFEAA7"} }导入脚本import_problems.py会读取题目 JSON 中的tags字段(如["dp", "math"]),自动调用 QDUOJ 的/api/admin/tag/接口创建标签,并关联到题目。效果是:后台题目列表页会出现彩色标签,前端题目详情页顶部显示动态规划 · 数学思维,教练用标签筛选比翻页快十倍。
4. 常见问题排查:那些让你怀疑人生却只需改一行配置的坑
4.1 现象:导入脚本报错HTTP 403 Forbidden,但管理员账号明明能登录后台
原因:QDUOJ 的 CSRF 保护机制默认开启。import_problems.py用requests直接 POST,没携带csrf_token,API 拒绝请求。
解决:在import_problems.py的session.post()前,先 GET 一次/api/admin/problem/获取csrftokencookie,再把它加到 headers:
# 在 import_problems.py 的 main 函数中 session.get(f"{base_url}/api/admin/problem/") # 触发 CSRF token 设置 headers = {"X-CSRFToken": session.cookies.get("csrftoken")} session.post(f"{base_url}/api/admin/problem/", json=data, headers=headers)4.2 现象:C/C++ 题目提交后一直显示 “Judging…”,日志里看到Compile Error: /usr/bin/gcc: not found
原因:QDUOJ 判题机容器(或宿主机)没装 GCC。很多人只装了build-essential,但忘了gcc包名在 Ubuntu 上是gcc,CentOS 上是gcc-c++。
解决:
- Ubuntu:
sudo apt install gcc g++ - CentOS:
sudo yum install gcc gcc-c++
然后重启判题服务:sudo systemctl restart qduoj-judge-server
4.3 现象:Python 题目提交后报Runtime Error,日志显示ModuleNotFoundError: No module named 'numpy'
原因:蓝桥杯 Python 组允许使用numpy,但 QDUOJ 默认沙箱环境只装了标准库。pypy3沙箱路径/tmp/judge_env/下没装第三方包。
解决:在判题服务器上,用pypy3 -m pip install numpy全局安装(注意:不是pip3,是pypy3 -m pip)。验证:pypy3 -c "import numpy; print(numpy.__version__)"。
4.4 现象:题目详情页显示 “样例输入/输出” 正确,但提交后所有测试点都是 WA,检查发现test_cases里的output末尾多了\r\n
原因:Windows 编辑器保存 JSON 时用了 CRLF 换行,Linux 判题机读取output字符串时包含\r,比对失败。
解决:批量清理 JSON 文件换行符:
find ../problem_data -name "*.json" -exec sed -i 's/\r$//' {} \;或者更彻底:用 Python 脚本重写所有 JSON,强制用\n:
import json for f in json_files: with open(f) as jf: data = json.load(jf) for tc in data.get("test_cases", []): tc["output"] = tc["output"].replace("\r\n", "\n").replace("\r", "\n") with open(f, "w") as jf: json.dump(data, jf, indent=2, ensure_ascii=False)4.5 现象:导入后题目列表里出现大量None标题,点进去 404
原因:JSON 文件编码不是 UTF-8。比如用 GBK 编辑器保存的题目描述含中文,import_problems.py用open(f, "r")默认按系统编码读,Linux 下是 UTF-8,读 GBK 文件就乱码,json.load()解析失败,title字段变成None。
解决:在import_problems.py的load_json函数里强制指定编码:
def load_json(file_path): with open(file_path, "r", encoding="utf-8") as f: # 显式声明 return json.load(f)并确保所有 JSON 文件用 UTF-8 无 BOM 格式保存(VS Code 右下角可切换)。
5. 二次开发实战:给蓝桥杯题库加“智能推荐”功能
5.1 需求场景:学生总卡在同一类题上,教练想按错误率动态推题
某高校教练反馈:学生反复错“DFS 剪枝”类题目,但题库现有标签只能静态筛选,无法根据实时错误数据推荐。我们需要一个轻量级推荐模块:当学生 A 在题目 P 上连续 WA ≥ 3 次,系统自动在首页推送同标签(如dfs)、同难度(±1 星)、AC 率 < 60% 的 3 道题。
不碰 QDUOJ 核心,只新增一个 Django Apprecommender:
cd QDUOJ python manage.py startapp recommender在recommender/models.py定义推荐规则:
from django.db import models from contest.models import Problem, Submission class RecommendationRule(models.Model): tag = models.CharField(max_length=20) # 对应 BLUEBRIDGE_TAGS 的 key difficulty_min = models.IntegerField(default=1) difficulty_max = models.IntegerField(default=5) max_ac_rate = models.FloatField(default=0.6) # 小于等于此值才推荐 created_at = models.DateTimeField(auto_now_add=True) class UserRecommendation(models.Model): user = models.ForeignKey("user.UserProfile", on_delete=models.CASCADE) problem = models.ForeignKey(Problem, on_delete=models.CASCADE) reason = models.TextField() # 如 "因在题目 '迷宫' 上连续 WA 3 次触发" created_at = models.DateTimeField(auto_now_add=True)5.2 关键逻辑:监听提交事件,触发推荐计算
QDUOJ 的提交记录存在Submission模型中。我们在recommender/apps.py中监听post_save信号:
# recommender/apps.py from django.apps import AppConfig from django.db.models.signals import post_save class RecommenderConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'recommender' def ready(self): from . import signals # 导入信号模块recommender/signals.py实现核心逻辑:
from django.db.models.signals import post_save from django.dispatch import receiver from contest.models import Submission, Problem from user.models import UserProfile from .models import UserRecommendation, RecommendationRule @receiver(post_save, sender=Submission) def trigger_recommendation(sender, instance, created, **kwargs): if not created or instance.result != "Wrong Answer": return # 查该用户最近 3 次 WA 是否都在同一题 recent_wa = Submission.objects.filter( user_id=instance.user_id, problem_id=instance.problem_id, result="Wrong Answer" ).order_by("-create_time")[:3] if recent_wa.count() < 3: return # 获取题目标签(一个题可能多个标签,取第一个) problem = instance.problem if not problem.tags.exists(): return first_tag = problem.tags.first().name # 注意:这里是 QDUOJ 原生 Tag 模型的 name 字段 # 查找匹配的推荐规则 rules = RecommendationRule.objects.filter( tag=first_tag, difficulty_min__lte=problem.difficulty, difficulty_max__gte=problem.difficulty, max_ac_rate__gte=problem.ac_rate # 题目 AC 率字段需提前在 Problem 模型加 ) if not rules.exists(): return # 推荐 3 道题(排除当前题和已推荐过的) recommended_problems = Problem.objects.filter( tags__name=first_tag, difficulty__range=(problem.difficulty-1, problem.difficulty+1), ac_rate__lt=rules.first().max_ac_rate ).exclude( id=problem.id ).exclude( id__in=UserRecommendation.objects.filter(user_id=instance.user_id).values_list('problem_id', flat=True) )[:3] for p in recommended_problems: UserRecommendation.objects.get_or_create( user_id=instance.user_id, problem=p, defaults={"reason": f"因在题目 '{problem.title}' 上连续 WA 3 次触发"} )注意:
Problem.ac_rate字段原生不存在,需在contest/models.py的Problem类中添加:ac_rate = models.FloatField(default=0.0, help_text="AC 率,由定时任务更新")并在
admin.py中注册,否则 Django Admin 看不见。
5.3 前端集成:在用户首页展示推荐题目
QDUOJ 前端用 Vue,首页模板在frontend/src/views/index/Index.vue。我们加一个推荐区块:
<!-- frontend/src/views/index/Index.vue --> <template> <!-- 原有内容 --> <div v-if="recommendations.length > 0" class="recommend-section"> <h3>💡 为你推荐</h3> <div class="problem-list"> <router-link v-for="r in recommendations" :key="r.id" :to="`/problem/${r.problem.id}`" class="problem-card" > <div class="title">{{ r.problem.title }}</div> <div class="meta"> <span class="tag">{{ r.problem.tags[0]?.name || '无标签' }}</span> <span class="difficulty">★{{ r.problem.difficulty }}</span> </div> <div class="reason">{{ r.reason }}</div> </router-link> </div> </div> </template> <script> export default { data() { return { recommendations: [] } }, async mounted() { try { const res = await this.$http.get('/api/recommendation/user/') this.recommendations = res.data.results } catch (e) { console.error('加载推荐失败', e) } } } </script>后端 API 在recommender/views.py:
from rest_framework.views import APIView from rest_framework.response import Response from .models import UserRecommendation class UserRecommendationAPIView(APIView): def get(self, request): if not request.user.is_authenticated: return Response({"results": []}) recs = UserRecommendation.objects.filter( user=request.user ).select_related('problem').order_by('-created_at')[:5] data = [{ "id": r.id, "problem": { "id": r.problem.id, "title": r.problem.title, "tags": [t.name for t in r.problem.tags.all()], "difficulty": r.problem.difficulty }, "reason": r.reason } for r in recs] return Response({"results": data})最后在urls.py中注册路由:
# QDUOJ/urls.py urlpatterns += [ path('api/recommendation/', include('recommender.urls')), ]5.4 部署与验证:三步上线,不用重启服务
迁移数据库:
python manage.py makemigrations recommender python manage.py migrate收集静态文件(Vue 前端):
cd frontend && npm run build && cd .. python manage.py collectstatic --noinput重启服务(只需重启 Django,判题机不用动):
sudo systemctl restart qduoj-web
验证方法:用测试账号提交一道题三次 WA,刷新首页,看是否出现推荐区块。日志里搜trigger_recommendation可确认信号是否触发。
从那以后我每次给竞赛平台加新功能,都强制走三步:先写最小可行模型(如只存推荐记录)、再加触发逻辑(信号监听)、最后补前端(Vue 组件)。绝不一上来就画大饼搞“AI 推荐引擎”。因为真实场景里,学生多点一次推荐链接,比算法提升 0.5% 的准确率更有价值。希望帮到你。
本文还有配套的精品资源,点击获取