diff --git a/SeedCppBasic_20_items.array.cc b/SeedCppBasic_20_items.array.cc new file mode 100644 index 0000000..20c9377 --- /dev/null +++ b/SeedCppBasic_20_items.array.cc @@ -0,0 +1,164 @@ +const CourseItem items[] = { + { + "cpp-basic-01-hello", + "C++基础01:环境配置与Hello World(VSCode)", + 1, + "course:cpp-basic:01", + "# C++基础01:环境配置与Hello World(VSCode)\n\n## 学习目标\n- 安装并打开 VSCode,创建并运行第一个 C++14 程序\n- 学会新建文件、保存、运行、查看输出\n- 了解 `main()`、`#include `、`cout`\n\n## 推荐视频(观看后写笔记)\n- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq)\n - https://www.bilibili.com/video/BV1tg411N7Fq/\n- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口)\n - https://www.bilibili.com/video/BV1dK4y137bk/\n\n## 参考图文\n- LoongBa 极简配置:GCC/VSCode/HelloWorld\n - https://github.com/LoongBa/Cpp_Beginner_Guide\n\n## 练习(完成至少 2 题)\n- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002\n- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000\n\n## 提交要求\n- 在本题页面下方“学习笔记”区域写下:\n 1) 你安装了什么、遇到什么坑、怎么解决\n 2) 你的 HelloWorld 代码\n 3) 你学到的 3 个关键词\n", + {"cpp-basic","vscode","io","", "", ""} + }, + { + "cpp-basic-02-io", + "C++基础02:输入输出与变量", + 1, + "course:cpp-basic:02", + "# C++基础02:输入输出与变量\n\n## 学习目标\n- 会用 `cin` 读入、`cout` 输出\n- 理解变量:`int / long long / double / char / string`\n\n## 推荐视频\n- 每天五分钟学C++:02 输出、03 变量(系列入口见上)\n - https://www.bilibili.com/video/BV1dK4y137bk/\n\n## 练习\n- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001\n- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008\n- P5704 字母转换 https://www.luogu.com.cn/problem/P5704\n\n## 提交要求\n- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行)\n", + {"cpp-basic","io","types","", "", ""} + }, + { + "cpp-basic-03-branch", + "C++基础03:分支结构(if / switch)", + 2, + "course:cpp-basic:03", + "# C++基础03:分支结构(if / switch)\n\n## 学习目标\n- 会写 `if / else if / else` 与基本逻辑运算\n- 能处理边界与分类讨论\n\n## 练习\n- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035\n- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711\n- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909\n\n## 提交要求\n- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件)\n", + {"cpp-basic","branch","logic","", "", ""} + }, + { + "cpp-basic-04-loop", + "C++基础04:循环结构(for / while)", + 2, + "course:cpp-basic:04", + "# C++基础04:循环结构(for / while)\n\n## 学习目标\n- 会用循环做:计数、累加、打印图形\n\n## 练习\n- B2083 画矩形 https://www.luogu.com.cn/problem/B2083\n- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421\n\n## 提交要求\n- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法\n", + {"cpp-basic","loop","debug","", "", ""} + }, + { + "cpp-basic-05-array", + "C++基础05:数组入门(一维)", + 3, + "course:cpp-basic:05", + "# C++基础05:数组入门(一维)\n\n## 学习目标\n- 会定义数组、遍历、统计\n\n## 练习\n- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427\n- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428\n\n## 提交要求\n- 笔记里写:数组下标从 0/1 的选择;如何避免越界\n", + {"cpp-basic","array","", "", "", ""} + }, + + // 06~20:新增课程 + { + "cpp-basic-06-char-string", + "C++基础06:字符与字符串(char / string)", + 3, + "course:cpp-basic:06", + "# C++基础06:字符与字符串(char / string)\n\n## 学习目标\n- 区分 `char`(一个字符)与 `string`(一串字符)\n- 会做:大小写转换、统计字母、简单加密\n\n## 重点\n- `char c='A'`,`string s=\"abc\"`\n- `s.size()` / `s[i]` / 遍历字符串\n\n## 练习(完成至少 2 题)\n- P5704 字母转换 https://www.luogu.com.cn/problem/P5704\n- P5733 自动修正 https://www.luogu.com.cn/problem/P5733\n- P1914 小书童——凯撒密码 https://www.luogu.com.cn/problem/P1914\n\n## 提交要求\n- 笔记写:`string` 下标访问与越界风险(最后一个下标是 size()-1)\n", + {"cpp-basic","string","char","", "", ""} + }, + { + "cpp-basic-07-operator-cast", + "C++基础07:运算、取整与类型转换(/ %)", + 3, + "course:cpp-basic:07", + "# C++基础07:运算、取整与类型转换(/ %)\n\n## 学习目标\n- 写对 `+ - * / %`\n- 理解:整数除法会向下取整;`%` 只能用于整数\n- 学会用 `long long` 防止溢出\n\n## 练习\n- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008\n- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421\n- P5708 三角形面积 https://www.luogu.com.cn/problem/P5708\n\n## 提交要求\n- 笔记写:什么时候要用 `long long`(例如钱、人数、乘法很大)\n", + {"cpp-basic","math","cast","", "", ""} + }, + { + "cpp-basic-08-logic", + "C++基础08:逻辑运算与边界(&& || !)", + 3, + "course:cpp-basic:08", + "# C++基础08:逻辑运算与边界(&& || !)\n\n## 学习目标\n- 能把题目里的“并且/或者/不是”写成条件\n- 学会找边界:等于、最小/最大、0\n\n## 练习\n- P5710 数的性质 https://www.luogu.com.cn/problem/P5710\n- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711\n- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909\n\n## 提交要求\n- 笔记写:你列出的边界测试用例(至少 3 个)\n", + {"cpp-basic","logic","boundary","", "", ""} + }, + { + "cpp-basic-09-switch", + "C++基础09:switch 与枚举列情况", + 3, + "course:cpp-basic:09", + "# C++基础09:switch 与枚举列情况\n\n## 学习目标\n- 会写 `switch/case/break`\n- 面对“情况很少”的题,会列举所有情况\n\n## 练习\n- P2433 小学数学N合一 https://www.luogu.com.cn/problem/P2433\n\n## 提交要求\n- 笔记写:为什么每个 case 后要 `break`(避免贯穿执行)\n", + {"cpp-basic","switch","enum","", "", ""} + }, + { + "cpp-basic-10-for", + "C++基础10:for循环(计数与累加)", + 3, + "course:cpp-basic:10", + "# C++基础10:for循环(计数与累加)\n\n## 学习目标\n- 熟练掌握 `for(初始化; 条件; 更新)`\n- 会写:求和、求最大最小、统计个数\n\n## 练习\n- P1425 小鱼的游泳时间 https://www.luogu.com.cn/problem/P1425\n- P5718 找最小值 https://www.luogu.com.cn/problem/P5718\n\n## 提交要求\n- 笔记写:循环“三要素”分别是什么\n", + {"cpp-basic","loop","for","", "", ""} + }, + { + "cpp-basic-11-while", + "C++基础11:while循环(未知次数)", + 3, + "course:cpp-basic:11", + "# C++基础11:while循环(未知次数)\n\n## 学习目标\n- 会用 `while` 处理:读到结束、读到 0 停止\n- 避免死循环:每次循环必须改变状态\n\n## 练习\n- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427\n- P1047 校门外的树(选做)https://www.luogu.com.cn/problem/P1047\n\n## 提交要求\n- 笔记写:你是如何避免死循环的(更新变量写在哪里)\n", + {"cpp-basic","loop","while","", "", ""} + }, + { + "cpp-basic-12-nested", + "C++基础12:嵌套循环与打印图形", + 3, + "course:cpp-basic:12", + "# C++基础12:嵌套循环与打印图形\n\n## 学习目标\n- 外层循环=行,内层循环=列\n- 能打印:矩形、三角形、乘法表\n\n## 练习\n- B2083 画矩形 https://www.luogu.com.cn/problem/B2083\n- P5717 三角形分类(练分类讨论 + 输出格式)https://www.luogu.com.cn/problem/P5717\n\n## 提交要求\n- 笔记写:你画图时是怎么想“行/列”的\n", + {"cpp-basic","nested-loop","print","", "", ""} + }, + { + "cpp-basic-13-array-ops", + "C++基础13:数组进阶(统计/查找/反转)", + 4, + "course:cpp-basic:13", + "# C++基础13:数组进阶(统计/查找/反转)\n\n## 学习目标\n- 用数组做:统计、查找位置、反转、去重(了解)\n\n## 练习\n- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428\n- P5727 冰雹猜想 https://www.luogu.com.cn/problem/P5727\n\n## 提交要求\n- 笔记写:数组越界的 2 个典型原因 + 你的检查方法\n", + {"cpp-basic","array","practice","", "", ""} + }, + { + "cpp-basic-14-function", + "C++基础14:函数入门(把重复代码变成工具)", + 4, + "course:cpp-basic:14", + "# C++基础14:函数入门(参数/返回值/作用域)\n\n## 学习目标\n- 会定义并调用函数\n- 知道局部变量作用域({} 内有效)\n\n## 练习\n- P5735 距离函数 https://www.luogu.com.cn/problem/P5735\n- P5739 计算阶乘(也可用递归)https://www.luogu.com.cn/problem/P5739\n\n## 提交要求\n- 笔记写:你把哪段重复代码抽成了函数?为什么这样更清晰?\n", + {"cpp-basic","function","scope","", "", ""} + }, + { + "cpp-basic-15-recursion", + "C++基础15:递归入门(选学)", + 4, + "course:cpp-basic:15", + "# C++基础15:递归入门(选学)\n\n## 学习目标\n- 理解递归:函数调用自己\n- 必须有终止条件(否则会无限调用)\n\n## 练习\n- P5739 计算阶乘 https://www.luogu.com.cn/problem/P5739\n- P1028 数的计算(选做)https://www.luogu.com.cn/problem/P1028\n\n## 提交要求\n- 笔记写:你的递归“终止条件”是什么?\n", + {"cpp-basic","recursion","", "", "", ""} + }, + { + "cpp-basic-16-struct", + "C++基础16:结构体 struct(选学)", + 4, + "course:cpp-basic:16", + "# C++基础16:结构体 struct(选学)\n\n## 学习目标\n- 用 `struct` 把多个字段打包成一个“人物卡/记录”\n- 学会 `struct` 数组\n\n## 练习\n- P5740 最厉害的学生 https://www.luogu.com.cn/problem/P5740\n- P5744 培训 https://www.luogu.com.cn/problem/P5744\n\n## 提交要求\n- 笔记写:struct 适合用在什么场景(多个属性属于同一个对象)\n", + {"cpp-basic","struct","", "", "", ""} + }, + { + "cpp-basic-17-sort", + "C++基础17:排序初步(sort)", + 4, + "course:cpp-basic:17", + "# C++基础17:排序初步(sort)\n\n## 学习目标\n- 会用 `#include ` 和 `sort`\n- 排序后做统计/查找会更简单\n\n## 练习\n- P1059 明明的随机数 https://www.luogu.com.cn/problem/P1059\n- P1093 奖学金(选做)https://www.luogu.com.cn/problem/P1093\n\n## 提交要求\n- 笔记写:sort 的区间是 `[begin, end)`(end 不包含)\n", + {"cpp-basic","sort","algorithm","", "", ""} + }, + { + "cpp-basic-18-binary-search", + "C++基础18:二分查找初步(lower_bound)", + 4, + "course:cpp-basic:18", + "# C++基础18:二分查找初步(lower_bound)\n\n## 学习目标\n- 理解“在有序数组里快速找答案”\n- 会用 `lower_bound` 找第一个 >= x 的位置\n\n## 练习\n- P2249 查找 https://www.luogu.com.cn/problem/P2249\n\n## 提交要求\n- 笔记写:二分查找的前提:数组必须有序\n", + {"cpp-basic","binary-search","", "", "", ""} + }, + { + "cpp-basic-19-prefix-sum", + "C++基础19:前缀和(区间求和神器)", + 4, + "course:cpp-basic:19", + "# C++基础19:前缀和(区间求和神器)\n\n## 学习目标\n- 会构造前缀和 `s[i]=s[i-1]+a[i]`\n- 会算区间和 `sum(l,r)=s[r]-s[l-1]`\n\n## 练习\n- P8218 求区间和(前缀和基础)https://www.luogu.com.cn/problem/P8218\n\n## 提交要求\n- 笔记写:下标偏移是怎么处理的(从 1 开始更方便)\n", + {"cpp-basic","prefix-sum","", "", "", ""} + }, + { + "cpp-basic-20-simulation", + "C++基础20:模拟题与综合复盘", + 4, + "course:cpp-basic:20", + "# C++基础20:模拟题与综合复盘\n\n## 学习目标\n- 学会按题意一步步做(模拟)\n- 复盘:总结常见错误 + 常用模板\n\n## 练习(从这里选 3~5 题做小测)\n- P1957 口算练习题 https://www.luogu.com.cn/problem/P1957\n- P1055 ISBN号码 https://www.luogu.com.cn/problem/P1055\n- P2433 小学数学N合一(回顾)https://www.luogu.com.cn/problem/P2433\n\n## 提交要求\n- 笔记写:\n 1) 你最常犯的 3 类错误\n 2) 你最常用的 5 行代码模板\n 3) 下周你准备怎么练\n", + {"cpp-basic","simulation","review","", "", ""} + }, +}; diff --git a/_csp.db.tmp b/_csp.db.tmp new file mode 100644 index 0000000..6b04962 Binary files /dev/null and b/_csp.db.tmp differ diff --git a/backend/src/db/sqlite_db.cc b/backend/src/db/sqlite_db.cc index 956f1fd..5d3b283 100644 --- a/backend/src/db/sqlite_db.cc +++ b/backend/src/db/sqlite_db.cc @@ -754,125 +754,170 @@ void SeedDemoData(SqliteDb& db) { const char* tags[6]; }; const CourseItem items[] = { - { - "cpp-basic-01-hello", - "C++基础01:环境配置与Hello World(VSCode)", - 1, - "course:cpp-basic:01", - R"MD(# C++基础01:环境配置与Hello World(VSCode) + { + "cpp-basic-01-hello", + "C++基础01:环境配置与Hello World(VSCode)", + 1, + "course:cpp-basic:01", + "# C++基础01:环境配置与Hello World(VSCode)\n\n## 学习目标\n- 安装并打开 VSCode,创建并运行第一个 C++14 程序\n- 学会新建文件、保存、运行、查看输出\n- 了解 `main()`、`#include `、`cout`\n\n## 推荐视频(观看后写笔记)\n- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq)\n - https://www.bilibili.com/video/BV1tg411N7Fq/\n- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口)\n - https://www.bilibili.com/video/BV1dK4y137bk/\n\n## 参考图文\n- LoongBa 极简配置:GCC/VSCode/HelloWorld\n - https://github.com/LoongBa/Cpp_Beginner_Guide\n\n## 练习(完成至少 2 题)\n- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002\n- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000\n\n## 提交要求\n- 在本题页面下方“学习笔记”区域写下:\n 1) 你安装了什么、遇到什么坑、怎么解决\n 2) 你的 HelloWorld 代码\n 3) 你学到的 3 个关键词\n", + {"cpp-basic","vscode","io","", "", ""} + }, + { + "cpp-basic-02-io", + "C++基础02:输入输出与变量", + 1, + "course:cpp-basic:02", + "# C++基础02:输入输出与变量\n\n## 学习目标\n- 会用 `cin` 读入、`cout` 输出\n- 理解变量:`int / long long / double / char / string`\n\n## 推荐视频\n- 每天五分钟学C++:02 输出、03 变量(系列入口见上)\n - https://www.bilibili.com/video/BV1dK4y137bk/\n\n## 练习\n- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001\n- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008\n- P5704 字母转换 https://www.luogu.com.cn/problem/P5704\n\n## 提交要求\n- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行)\n", + {"cpp-basic","io","types","", "", ""} + }, + { + "cpp-basic-03-branch", + "C++基础03:分支结构(if / switch)", + 2, + "course:cpp-basic:03", + "# C++基础03:分支结构(if / switch)\n\n## 学习目标\n- 会写 `if / else if / else` 与基本逻辑运算\n- 能处理边界与分类讨论\n\n## 练习\n- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035\n- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711\n- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909\n\n## 提交要求\n- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件)\n", + {"cpp-basic","branch","logic","", "", ""} + }, + { + "cpp-basic-04-loop", + "C++基础04:循环结构(for / while)", + 2, + "course:cpp-basic:04", + "# C++基础04:循环结构(for / while)\n\n## 学习目标\n- 会用循环做:计数、累加、打印图形\n\n## 练习\n- B2083 画矩形 https://www.luogu.com.cn/problem/B2083\n- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421\n\n## 提交要求\n- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法\n", + {"cpp-basic","loop","debug","", "", ""} + }, + { + "cpp-basic-05-array", + "C++基础05:数组入门(一维)", + 3, + "course:cpp-basic:05", + "# C++基础05:数组入门(一维)\n\n## 学习目标\n- 会定义数组、遍历、统计\n\n## 练习\n- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427\n- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428\n\n## 提交要求\n- 笔记里写:数组下标从 0/1 的选择;如何避免越界\n", + {"cpp-basic","array","", "", "", ""} + }, -## 学习目标 -- 安装并打开 VSCode,创建并运行第一个 C++14 程序 -- 学会新建文件、保存、运行、查看输出 -- 了解 `main()`、`#include `、`cout` + // 06~20:新增课程 + { + "cpp-basic-06-char-string", + "C++基础06:字符与字符串(char / string)", + 3, + "course:cpp-basic:06", + "# C++基础06:字符与字符串(char / string)\n\n## 学习目标\n- 区分 `char`(一个字符)与 `string`(一串字符)\n- 会做:大小写转换、统计字母、简单加密\n\n## 重点\n- `char c='A'`,`string s=\"abc\"`\n- `s.size()` / `s[i]` / 遍历字符串\n\n## 练习(完成至少 2 题)\n- P5704 字母转换 https://www.luogu.com.cn/problem/P5704\n- P5733 自动修正 https://www.luogu.com.cn/problem/P5733\n- P1914 小书童——凯撒密码 https://www.luogu.com.cn/problem/P1914\n\n## 提交要求\n- 笔记写:`string` 下标访问与越界风险(最后一个下标是 size()-1)\n", + {"cpp-basic","string","char","", "", ""} + }, + { + "cpp-basic-07-operator-cast", + "C++基础07:运算、取整与类型转换(/ %)", + 3, + "course:cpp-basic:07", + "# C++基础07:运算、取整与类型转换(/ %)\n\n## 学习目标\n- 写对 `+ - * / %`\n- 理解:整数除法会向下取整;`%` 只能用于整数\n- 学会用 `long long` 防止溢出\n\n## 练习\n- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008\n- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421\n- P5708 三角形面积 https://www.luogu.com.cn/problem/P5708\n\n## 提交要求\n- 笔记写:什么时候要用 `long long`(例如钱、人数、乘法很大)\n", + {"cpp-basic","math","cast","", "", ""} + }, + { + "cpp-basic-08-logic", + "C++基础08:逻辑运算与边界(&& || !)", + 3, + "course:cpp-basic:08", + "# C++基础08:逻辑运算与边界(&& || !)\n\n## 学习目标\n- 能把题目里的“并且/或者/不是”写成条件\n- 学会找边界:等于、最小/最大、0\n\n## 练习\n- P5710 数的性质 https://www.luogu.com.cn/problem/P5710\n- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711\n- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909\n\n## 提交要求\n- 笔记写:你列出的边界测试用例(至少 3 个)\n", + {"cpp-basic","logic","boundary","", "", ""} + }, + { + "cpp-basic-09-switch", + "C++基础09:switch 与枚举列情况", + 3, + "course:cpp-basic:09", + "# C++基础09:switch 与枚举列情况\n\n## 学习目标\n- 会写 `switch/case/break`\n- 面对“情况很少”的题,会列举所有情况\n\n## 练习\n- P2433 小学数学N合一 https://www.luogu.com.cn/problem/P2433\n\n## 提交要求\n- 笔记写:为什么每个 case 后要 `break`(避免贯穿执行)\n", + {"cpp-basic","switch","enum","", "", ""} + }, + { + "cpp-basic-10-for", + "C++基础10:for循环(计数与累加)", + 3, + "course:cpp-basic:10", + "# C++基础10:for循环(计数与累加)\n\n## 学习目标\n- 熟练掌握 `for(初始化; 条件; 更新)`\n- 会写:求和、求最大最小、统计个数\n\n## 练习\n- P1425 小鱼的游泳时间 https://www.luogu.com.cn/problem/P1425\n- P5718 找最小值 https://www.luogu.com.cn/problem/P5718\n\n## 提交要求\n- 笔记写:循环“三要素”分别是什么\n", + {"cpp-basic","loop","for","", "", ""} + }, + { + "cpp-basic-11-while", + "C++基础11:while循环(未知次数)", + 3, + "course:cpp-basic:11", + "# C++基础11:while循环(未知次数)\n\n## 学习目标\n- 会用 `while` 处理:读到结束、读到 0 停止\n- 避免死循环:每次循环必须改变状态\n\n## 练习\n- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427\n- P1047 校门外的树(选做)https://www.luogu.com.cn/problem/P1047\n\n## 提交要求\n- 笔记写:你是如何避免死循环的(更新变量写在哪里)\n", + {"cpp-basic","loop","while","", "", ""} + }, + { + "cpp-basic-12-nested", + "C++基础12:嵌套循环与打印图形", + 3, + "course:cpp-basic:12", + "# C++基础12:嵌套循环与打印图形\n\n## 学习目标\n- 外层循环=行,内层循环=列\n- 能打印:矩形、三角形、乘法表\n\n## 练习\n- B2083 画矩形 https://www.luogu.com.cn/problem/B2083\n- P5717 三角形分类(练分类讨论 + 输出格式)https://www.luogu.com.cn/problem/P5717\n\n## 提交要求\n- 笔记写:你画图时是怎么想“行/列”的\n", + {"cpp-basic","nested-loop","print","", "", ""} + }, + { + "cpp-basic-13-array-ops", + "C++基础13:数组进阶(统计/查找/反转)", + 4, + "course:cpp-basic:13", + "# C++基础13:数组进阶(统计/查找/反转)\n\n## 学习目标\n- 用数组做:统计、查找位置、反转、去重(了解)\n\n## 练习\n- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428\n- P5727 冰雹猜想 https://www.luogu.com.cn/problem/P5727\n\n## 提交要求\n- 笔记写:数组越界的 2 个典型原因 + 你的检查方法\n", + {"cpp-basic","array","practice","", "", ""} + }, + { + "cpp-basic-14-function", + "C++基础14:函数入门(把重复代码变成工具)", + 4, + "course:cpp-basic:14", + "# C++基础14:函数入门(参数/返回值/作用域)\n\n## 学习目标\n- 会定义并调用函数\n- 知道局部变量作用域({} 内有效)\n\n## 练习\n- P5735 距离函数 https://www.luogu.com.cn/problem/P5735\n- P5739 计算阶乘(也可用递归)https://www.luogu.com.cn/problem/P5739\n\n## 提交要求\n- 笔记写:你把哪段重复代码抽成了函数?为什么这样更清晰?\n", + {"cpp-basic","function","scope","", "", ""} + }, + { + "cpp-basic-15-recursion", + "C++基础15:递归入门(选学)", + 4, + "course:cpp-basic:15", + "# C++基础15:递归入门(选学)\n\n## 学习目标\n- 理解递归:函数调用自己\n- 必须有终止条件(否则会无限调用)\n\n## 练习\n- P5739 计算阶乘 https://www.luogu.com.cn/problem/P5739\n- P1028 数的计算(选做)https://www.luogu.com.cn/problem/P1028\n\n## 提交要求\n- 笔记写:你的递归“终止条件”是什么?\n", + {"cpp-basic","recursion","", "", "", ""} + }, + { + "cpp-basic-16-struct", + "C++基础16:结构体 struct(选学)", + 4, + "course:cpp-basic:16", + "# C++基础16:结构体 struct(选学)\n\n## 学习目标\n- 用 `struct` 把多个字段打包成一个“人物卡/记录”\n- 学会 `struct` 数组\n\n## 练习\n- P5740 最厉害的学生 https://www.luogu.com.cn/problem/P5740\n- P5744 培训 https://www.luogu.com.cn/problem/P5744\n\n## 提交要求\n- 笔记写:struct 适合用在什么场景(多个属性属于同一个对象)\n", + {"cpp-basic","struct","", "", "", ""} + }, + { + "cpp-basic-17-sort", + "C++基础17:排序初步(sort)", + 4, + "course:cpp-basic:17", + "# C++基础17:排序初步(sort)\n\n## 学习目标\n- 会用 `#include ` 和 `sort`\n- 排序后做统计/查找会更简单\n\n## 练习\n- P1059 明明的随机数 https://www.luogu.com.cn/problem/P1059\n- P1093 奖学金(选做)https://www.luogu.com.cn/problem/P1093\n\n## 提交要求\n- 笔记写:sort 的区间是 `[begin, end)`(end 不包含)\n", + {"cpp-basic","sort","algorithm","", "", ""} + }, + { + "cpp-basic-18-binary-search", + "C++基础18:二分查找初步(lower_bound)", + 4, + "course:cpp-basic:18", + "# C++基础18:二分查找初步(lower_bound)\n\n## 学习目标\n- 理解“在有序数组里快速找答案”\n- 会用 `lower_bound` 找第一个 >= x 的位置\n\n## 练习\n- P2249 查找 https://www.luogu.com.cn/problem/P2249\n\n## 提交要求\n- 笔记写:二分查找的前提:数组必须有序\n", + {"cpp-basic","binary-search","", "", "", ""} + }, + { + "cpp-basic-19-prefix-sum", + "C++基础19:前缀和(区间求和神器)", + 4, + "course:cpp-basic:19", + "# C++基础19:前缀和(区间求和神器)\n\n## 学习目标\n- 会构造前缀和 `s[i]=s[i-1]+a[i]`\n- 会算区间和 `sum(l,r)=s[r]-s[l-1]`\n\n## 练习\n- P8218 求区间和(前缀和基础)https://www.luogu.com.cn/problem/P8218\n\n## 提交要求\n- 笔记写:下标偏移是怎么处理的(从 1 开始更方便)\n", + {"cpp-basic","prefix-sum","", "", "", ""} + }, + { + "cpp-basic-20-simulation", + "C++基础20:模拟题与综合复盘", + 4, + "course:cpp-basic:20", + "# C++基础20:模拟题与综合复盘\n\n## 学习目标\n- 学会按题意一步步做(模拟)\n- 复盘:总结常见错误 + 常用模板\n\n## 练习(从这里选 3~5 题做小测)\n- P1957 口算练习题 https://www.luogu.com.cn/problem/P1957\n- P1055 ISBN号码 https://www.luogu.com.cn/problem/P1055\n- P2433 小学数学N合一(回顾)https://www.luogu.com.cn/problem/P2433\n\n## 提交要求\n- 笔记写:\n 1) 你最常犯的 3 类错误\n 2) 你最常用的 5 行代码模板\n 3) 下周你准备怎么练\n", + {"cpp-basic","simulation","review","", "", ""} + }, +}; -## 推荐视频(观看后写笔记) -- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq) - - https://www.bilibili.com/video/BV1tg411N7Fq/ -- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口) - - https://www.bilibili.com/video/BV1dK4y137bk/ - -## 参考图文 -- LoongBa 极简配置:GCC/VSCode/HelloWorld - - https://github.com/LoongBa/Cpp_Beginner_Guide - -## 练习(完成至少 2 题) -- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002 -- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000 - -## 提交要求 -- 在本题页面下方“学习笔记”区域写下: - 1) 你安装了什么、遇到什么坑、怎么解决 - 2) 你的 HelloWorld 代码 - 3) 你学到的 3 个关键词 -)MD", - {"cpp-basic", "vscode", "io", "", "", ""} - }, - { - "cpp-basic-02-io", - "C++基础02:输入输出与变量", - 1, - "course:cpp-basic:02", - R"MD(# C++基础02:输入输出与变量 - -## 学习目标 -- 会用 `cin` 读入、`cout` 输出 -- 理解变量:`int / long long / double / char / string` - -## 推荐视频 -- 每天五分钟学C++:02 输出、03 变量(系列入口见上) - - https://www.bilibili.com/video/BV1dK4y137bk/ - -## 练习 -- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001 -- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008 -- P5704 字母转换 https://www.luogu.com.cn/problem/P5704 - -## 提交要求 -- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行) -)MD", - {"cpp-basic", "io", "types", "", "", ""} - }, - { - "cpp-basic-03-branch", - "C++基础03:分支结构(if / switch)", - 2, - "course:cpp-basic:03", - R"MD(# C++基础03:分支结构(if / switch) - -## 学习目标 -- 会写 `if / else if / else` 与基本逻辑运算 -- 能处理边界与分类讨论 - -## 练习 -- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035 -- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711 -- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909 - -## 提交要求 -- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件) -)MD", - {"cpp-basic", "branch", "logic", "", "", ""} - }, - { - "cpp-basic-04-loop", - "C++基础04:循环结构(for / while)", - 2, - "course:cpp-basic:04", - R"MD(# C++基础04:循环结构(for / while) - -## 学习目标 -- 会用循环做:计数、累加、打印图形 - -## 练习 -- B2083 画矩形 https://www.luogu.com.cn/problem/B2083 -- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421 - -## 提交要求 -- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法 -)MD", - {"cpp-basic", "loop", "debug", "", "", ""} - }, - { - "cpp-basic-05-array", - "C++基础05:数组入门(一维)", - 3, - "course:cpp-basic:05", - R"MD(# C++基础05:数组入门(一维) - -## 学习目标 -- 会定义数组、遍历、统计 - -## 练习 -- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427 -- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428 - -## 提交要求 -- 笔记里写:数组下标从 0/1 的选择;如何避免越界 -)MD", - {"cpp-basic", "array", "", "", "", ""} - } - }; for (const auto& it : items) { InsertProblem(raw, it.slug, it.title, it.md, it.diff, it.source, "", "", created); diff --git a/backend/src/db/sqlite_db.cc.bak_cppbasic20 b/backend/src/db/sqlite_db.cc.bak_cppbasic20 new file mode 100644 index 0000000..956f1fd --- /dev/null +++ b/backend/src/db/sqlite_db.cc.bak_cppbasic20 @@ -0,0 +1,892 @@ +#include "csp/db/sqlite_db.h" + +#include +#include +#include +#include +#include + +namespace csp::db { + +namespace { + +void ThrowSqlite(int rc, sqlite3* db, const char* what) { + if (rc == SQLITE_OK || rc == SQLITE_DONE || rc == SQLITE_ROW) return; + const char* msg = db ? sqlite3_errmsg(db) : ""; + throw std::runtime_error(std::string(what) + ": " + msg); +} + +int64_t NowSec() { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +bool ColumnExists(sqlite3* db, const char* table, const char* col) { + sqlite3_stmt* stmt = nullptr; + const std::string sql = std::string("PRAGMA table_info(") + table + ")"; + const int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + if (stmt) sqlite3_finalize(stmt); + return false; + } + + bool found = false; + while (sqlite3_step(stmt) == SQLITE_ROW) { + const unsigned char* name = sqlite3_column_text(stmt, 1); + if (name && std::string(reinterpret_cast(name)) == col) { + found = true; + break; + } + } + sqlite3_finalize(stmt); + return found; +} + +void EnsureColumn(SqliteDb& db, + const char* table, + const char* col_name, + const char* col_def) { + if (ColumnExists(db.raw(), table, col_name)) return; + db.Exec(std::string("ALTER TABLE ") + table + " ADD COLUMN " + col_def + ";"); +} + +int CountRows(sqlite3* db, const char* table) { + sqlite3_stmt* stmt = nullptr; + const std::string sql = std::string("SELECT COUNT(1) FROM ") + table; + int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + if (stmt) sqlite3_finalize(stmt); + return 0; + } + + rc = sqlite3_step(stmt); + int count = 0; + if (rc == SQLITE_ROW) count = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return count; +} + +std::optional QueryOneId(sqlite3* db, const std::string& sql) { + sqlite3_stmt* stmt = nullptr; + const int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); + if (rc != SQLITE_OK) { + if (stmt) sqlite3_finalize(stmt); + return std::nullopt; + } + if (sqlite3_step(stmt) != SQLITE_ROW) { + sqlite3_finalize(stmt); + return std::nullopt; + } + const auto id = sqlite3_column_int64(stmt, 0); + sqlite3_finalize(stmt); + return id; +} + +void InsertProblem(sqlite3* db, + const std::string& slug, + const std::string& title, + const std::string& statement, + int difficulty, + const std::string& source, + const std::string& sample_in, + const std::string& sample_out, + int64_t created_at) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT INTO problems(slug,title,statement_md,difficulty,source,sample_input,sample_output,created_at) " + "VALUES(?,?,?,?,?,?,?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert problem"); + ThrowSqlite(sqlite3_bind_text(stmt, 1, slug.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.slug"); + ThrowSqlite(sqlite3_bind_text(stmt, 2, title.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.title"); + ThrowSqlite(sqlite3_bind_text(stmt, 3, statement.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.statement"); + ThrowSqlite(sqlite3_bind_int(stmt, 4, difficulty), db, + "bind problem.difficulty"); + ThrowSqlite(sqlite3_bind_text(stmt, 5, source.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.source"); + ThrowSqlite(sqlite3_bind_text(stmt, 6, sample_in.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.sample_input"); + ThrowSqlite(sqlite3_bind_text(stmt, 7, sample_out.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem.sample_output"); + ThrowSqlite(sqlite3_bind_int64(stmt, 8, created_at), db, + "bind problem.created_at"); + ThrowSqlite(sqlite3_step(stmt), db, "insert problem"); + sqlite3_finalize(stmt); +} + +void InsertProblemTag(sqlite3* db, int64_t problem_id, const std::string& tag) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES(?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert problem_tag"); + ThrowSqlite(sqlite3_bind_int64(stmt, 1, problem_id), db, + "bind problem_tag.problem_id"); + ThrowSqlite(sqlite3_bind_text(stmt, 2, tag.c_str(), -1, SQLITE_TRANSIENT), db, + "bind problem_tag.tag"); + ThrowSqlite(sqlite3_step(stmt), db, "insert problem_tag"); + sqlite3_finalize(stmt); +} + +void InsertKbArticle(sqlite3* db, + const std::string& slug, + const std::string& title, + const std::string& content_md, + int64_t created_at) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT INTO kb_articles(slug,title,content_md,created_at) VALUES(?,?,?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert kb_article"); + ThrowSqlite(sqlite3_bind_text(stmt, 1, slug.c_str(), -1, SQLITE_TRANSIENT), db, + "bind kb_article.slug"); + ThrowSqlite(sqlite3_bind_text(stmt, 2, title.c_str(), -1, SQLITE_TRANSIENT), db, + "bind kb_article.title"); + ThrowSqlite(sqlite3_bind_text(stmt, 3, content_md.c_str(), -1, SQLITE_TRANSIENT), db, + "bind kb_article.content"); + ThrowSqlite(sqlite3_bind_int64(stmt, 4, created_at), db, + "bind kb_article.created_at"); + ThrowSqlite(sqlite3_step(stmt), db, "insert kb_article"); + sqlite3_finalize(stmt); +} + +void InsertKbLink(sqlite3* db, int64_t article_id, int64_t problem_id) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT OR IGNORE INTO kb_article_links(article_id,problem_id) VALUES(?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert kb_article_link"); + ThrowSqlite(sqlite3_bind_int64(stmt, 1, article_id), db, + "bind kb_article_link.article_id"); + ThrowSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, + "bind kb_article_link.problem_id"); + ThrowSqlite(sqlite3_step(stmt), db, "insert kb_article_link"); + sqlite3_finalize(stmt); +} + +void InsertContest(sqlite3* db, + const std::string& title, + int64_t starts_at, + int64_t ends_at, + const std::string& rule_json) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT INTO contests(title,starts_at,ends_at,rule_json) VALUES(?,?,?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert contest"); + ThrowSqlite(sqlite3_bind_text(stmt, 1, title.c_str(), -1, SQLITE_TRANSIENT), db, + "bind contest.title"); + ThrowSqlite(sqlite3_bind_int64(stmt, 2, starts_at), db, + "bind contest.starts_at"); + ThrowSqlite(sqlite3_bind_int64(stmt, 3, ends_at), db, + "bind contest.ends_at"); + ThrowSqlite(sqlite3_bind_text(stmt, 4, rule_json.c_str(), -1, SQLITE_TRANSIENT), db, + "bind contest.rule_json"); + ThrowSqlite(sqlite3_step(stmt), db, "insert contest"); + sqlite3_finalize(stmt); +} + +void InsertContestProblem(sqlite3* db, + int64_t contest_id, + int64_t problem_id, + int idx) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT OR IGNORE INTO contest_problems(contest_id,problem_id,idx) VALUES(?,?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert contest_problem"); + ThrowSqlite(sqlite3_bind_int64(stmt, 1, contest_id), db, + "bind contest_problem.contest_id"); + ThrowSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, + "bind contest_problem.problem_id"); + ThrowSqlite(sqlite3_bind_int(stmt, 3, idx), db, + "bind contest_problem.idx"); + ThrowSqlite(sqlite3_step(stmt), db, "insert contest_problem"); + sqlite3_finalize(stmt); +} + +void InsertRedeemItem(sqlite3* db, + const std::string& name, + const std::string& description, + const std::string& unit_label, + int holiday_cost, + int studyday_cost, + int is_active, + int is_global, + int64_t created_by, + int64_t created_at) { + sqlite3_stmt* stmt = nullptr; + const char* sql = + "INSERT INTO redeem_items(" + "name,description,unit_label,holiday_cost,studyday_cost,is_active,is_global,created_by,created_at,updated_at" + ") VALUES(?,?,?,?,?,?,?,?,?,?)"; + ThrowSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, + "prepare insert redeem_item"); + ThrowSqlite(sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT), db, + "bind redeem_item.name"); + ThrowSqlite(sqlite3_bind_text(stmt, 2, description.c_str(), -1, SQLITE_TRANSIENT), + db, "bind redeem_item.description"); + ThrowSqlite(sqlite3_bind_text(stmt, 3, unit_label.c_str(), -1, SQLITE_TRANSIENT), + db, "bind redeem_item.unit_label"); + ThrowSqlite(sqlite3_bind_int(stmt, 4, holiday_cost), db, + "bind redeem_item.holiday_cost"); + ThrowSqlite(sqlite3_bind_int(stmt, 5, studyday_cost), db, + "bind redeem_item.studyday_cost"); + ThrowSqlite(sqlite3_bind_int(stmt, 6, is_active), db, + "bind redeem_item.is_active"); + ThrowSqlite(sqlite3_bind_int(stmt, 7, is_global), db, + "bind redeem_item.is_global"); + ThrowSqlite(sqlite3_bind_int64(stmt, 8, created_by), db, + "bind redeem_item.created_by"); + ThrowSqlite(sqlite3_bind_int64(stmt, 9, created_at), db, + "bind redeem_item.created_at"); + ThrowSqlite(sqlite3_bind_int64(stmt, 10, created_at), db, + "bind redeem_item.updated_at"); + ThrowSqlite(sqlite3_step(stmt), db, "insert redeem_item"); + sqlite3_finalize(stmt); +} + +} // namespace + +SqliteDb SqliteDb::OpenFile(const std::string& path) { + sqlite3* db = nullptr; + const int rc = sqlite3_open(path.c_str(), &db); + if (rc != SQLITE_OK) { + const char* msg = db ? sqlite3_errmsg(db) : ""; + if (db) sqlite3_close(db); + throw std::runtime_error(std::string("sqlite3_open failed: ") + msg); + } + return SqliteDb(db); +} + +SqliteDb SqliteDb::OpenMemory() { + sqlite3* db = nullptr; + const int rc = sqlite3_open(":memory:", &db); + ThrowSqlite(rc, db, "sqlite3_open(:memory:) failed"); + return SqliteDb(db); +} + +SqliteDb::~SqliteDb() { + if (db_) sqlite3_close(db_); +} + +SqliteDb::SqliteDb(SqliteDb&& other) noexcept : db_(other.db_) { + other.db_ = nullptr; +} + +SqliteDb& SqliteDb::operator=(SqliteDb&& other) noexcept { + if (this == &other) return *this; + if (db_) sqlite3_close(db_); + db_ = other.db_; + other.db_ = nullptr; + return *this; +} + +void SqliteDb::Exec(const std::string& sql) { + char* err = nullptr; + const int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err); + if (rc != SQLITE_OK) { + std::string msg = err ? err : ""; + sqlite3_free(err); + ThrowSqlite(rc, db_, msg.c_str()); + } +} + +void ApplyMigrations(SqliteDb& db) { + // Keep it simple for MVP: create missing tables, then patch missing columns. + db.Exec("PRAGMA foreign_keys = ON;"); + + db.Exec(R"SQL( +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password_salt TEXT NOT NULL, + password_hash TEXT NOT NULL, + rating INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS sessions ( + token TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + expires_at INTEGER NOT NULL, + created_at INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS problems ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + slug TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + statement_md TEXT NOT NULL, + difficulty INTEGER NOT NULL DEFAULT 1, + source TEXT NOT NULL DEFAULT "", + statement_url TEXT NOT NULL DEFAULT "", + llm_profile_json TEXT NOT NULL DEFAULT "{}", + sample_input TEXT NOT NULL DEFAULT "", + sample_output TEXT NOT NULL DEFAULT "", + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS problem_tags ( + problem_id INTEGER NOT NULL, + tag TEXT NOT NULL, + PRIMARY KEY(problem_id, tag), + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS submissions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + contest_id INTEGER, + language TEXT NOT NULL, + code TEXT NOT NULL, + status TEXT NOT NULL, + score INTEGER NOT NULL DEFAULT 0, + time_ms INTEGER NOT NULL DEFAULT 0, + memory_kb INTEGER NOT NULL DEFAULT 0, + compile_log TEXT NOT NULL DEFAULT "", + runtime_log TEXT NOT NULL DEFAULT "", + created_at INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE, + FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS wrong_book ( + user_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + last_submission_id INTEGER, + note TEXT NOT NULL DEFAULT "", + note_score INTEGER NOT NULL DEFAULT 0, + note_rating INTEGER NOT NULL DEFAULT 0, + note_feedback_md TEXT NOT NULL DEFAULT "", + note_scored_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL, + PRIMARY KEY(user_id, problem_id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE, + FOREIGN KEY(last_submission_id) REFERENCES submissions(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS contests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + starts_at INTEGER NOT NULL, + ends_at INTEGER NOT NULL, + rule_json TEXT NOT NULL DEFAULT "{}" +); + +CREATE TABLE IF NOT EXISTS contest_problems ( + contest_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + idx INTEGER NOT NULL, + PRIMARY KEY(contest_id, problem_id), + FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS contest_registrations ( + contest_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + registered_at INTEGER NOT NULL, + PRIMARY KEY(contest_id, user_id), + FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS kb_articles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + slug TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + content_md TEXT NOT NULL, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS kb_article_links ( + article_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + PRIMARY KEY(article_id, problem_id), + FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS import_jobs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + status TEXT NOT NULL, + trigger TEXT NOT NULL DEFAULT "manual", + total_count INTEGER NOT NULL DEFAULT 0, + processed_count INTEGER NOT NULL DEFAULT 0, + success_count INTEGER NOT NULL DEFAULT 0, + failed_count INTEGER NOT NULL DEFAULT 0, + options_json TEXT NOT NULL DEFAULT "{}", + last_error TEXT NOT NULL DEFAULT "", + started_at INTEGER NOT NULL, + finished_at INTEGER, + updated_at INTEGER NOT NULL, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS import_job_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + job_id INTEGER NOT NULL, + source_path TEXT NOT NULL, + status TEXT NOT NULL DEFAULT "queued", + title TEXT NOT NULL DEFAULT "", + difficulty INTEGER NOT NULL DEFAULT 0, + problem_id INTEGER, + error_text TEXT NOT NULL DEFAULT "", + started_at INTEGER, + finished_at INTEGER, + updated_at INTEGER NOT NULL, + created_at INTEGER NOT NULL, + FOREIGN KEY(job_id) REFERENCES import_jobs(id) ON DELETE CASCADE, + UNIQUE(job_id, source_path) +); + +CREATE TABLE IF NOT EXISTS problem_drafts ( + user_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + language TEXT NOT NULL DEFAULT "cpp", + code TEXT NOT NULL DEFAULT "", + stdin TEXT NOT NULL DEFAULT "", + updated_at INTEGER NOT NULL, + created_at INTEGER NOT NULL, + PRIMARY KEY(user_id, problem_id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS problem_solution_jobs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + problem_id INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT "queued", + progress INTEGER NOT NULL DEFAULT 0, + message TEXT NOT NULL DEFAULT "", + created_by INTEGER NOT NULL DEFAULT 0, + max_solutions INTEGER NOT NULL DEFAULT 3, + created_at INTEGER NOT NULL, + started_at INTEGER, + finished_at INTEGER, + updated_at INTEGER NOT NULL, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS problem_solutions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + problem_id INTEGER NOT NULL, + variant INTEGER NOT NULL DEFAULT 1, + title TEXT NOT NULL DEFAULT "", + idea_md TEXT NOT NULL DEFAULT "", + explanation_md TEXT NOT NULL DEFAULT "", + code_cpp TEXT NOT NULL DEFAULT "", + complexity TEXT NOT NULL DEFAULT "", + tags_json TEXT NOT NULL DEFAULT "[]", + source TEXT NOT NULL DEFAULT "llm", + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS problem_solution_view_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + problem_id INTEGER NOT NULL, + day_key TEXT NOT NULL, + viewed_at INTEGER NOT NULL, + charged INTEGER NOT NULL DEFAULT 0, + cost INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS submission_feedback ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + submission_id INTEGER NOT NULL UNIQUE, + feedback_md TEXT NOT NULL DEFAULT "", + links_json TEXT NOT NULL DEFAULT "[]", + model_name TEXT NOT NULL DEFAULT "", + status TEXT NOT NULL DEFAULT "ready", + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + FOREIGN KEY(submission_id) REFERENCES submissions(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS redeem_items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT NOT NULL DEFAULT "", + unit_label TEXT NOT NULL DEFAULT "小时", + holiday_cost INTEGER NOT NULL DEFAULT 5, + studyday_cost INTEGER NOT NULL DEFAULT 25, + duration_minutes INTEGER NOT NULL DEFAULT 0, + is_active INTEGER NOT NULL DEFAULT 1, + is_global INTEGER NOT NULL DEFAULT 1, + created_by INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS redeem_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + item_name TEXT NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + day_type TEXT NOT NULL DEFAULT "studyday", + unit_cost INTEGER NOT NULL DEFAULT 0, + total_cost INTEGER NOT NULL DEFAULT 0, + note TEXT NOT NULL DEFAULT "", + created_at INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(item_id) REFERENCES redeem_items(id) ON DELETE RESTRICT +); + +CREATE TABLE IF NOT EXISTS daily_task_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + task_code TEXT NOT NULL, + day_key TEXT NOT NULL, + reward INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(user_id, task_code, day_key) +); + )SQL"); + + // Backward-compatible schema upgrades for existing deployments. + EnsureColumn(db, "problems", "sample_input", + "sample_input TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problems", "sample_output", + "sample_output TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problems", "statement_url", + "statement_url TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problems", "llm_profile_json", + "llm_profile_json TEXT NOT NULL DEFAULT '{}'"); + EnsureColumn(db, "import_jobs", "trigger", + "trigger TEXT NOT NULL DEFAULT 'manual'"); + EnsureColumn(db, "import_jobs", "options_json", + "options_json TEXT NOT NULL DEFAULT '{}'"); + EnsureColumn(db, "import_jobs", "last_error", + "last_error TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "submissions", "contest_id", "contest_id INTEGER"); + EnsureColumn(db, "submissions", "compile_log", + "compile_log TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "submissions", "runtime_log", + "runtime_log TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_drafts", "stdin", "stdin TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_solution_jobs", "max_solutions", + "max_solutions INTEGER NOT NULL DEFAULT 3"); + EnsureColumn(db, "wrong_book", "note_score", "note_score INTEGER NOT NULL DEFAULT 0"); + EnsureColumn(db, "wrong_book", "note_rating", "note_rating INTEGER NOT NULL DEFAULT 0"); + EnsureColumn(db, "wrong_book", "note_feedback_md", "note_feedback_md TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "wrong_book", "note_scored_at", "note_scored_at INTEGER NOT NULL DEFAULT 0"); + EnsureColumn(db, "wrong_book", "note_images_json", "note_images_json TEXT NOT NULL DEFAULT '[]'"); + EnsureColumn(db, "problem_solutions", "variant", "variant INTEGER NOT NULL DEFAULT 1"); + EnsureColumn(db, "problem_solutions", "idea_md", "idea_md TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_solutions", "explanation_md", + "explanation_md TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_solutions", "code_cpp", "code_cpp TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_solutions", "complexity", + "complexity TEXT NOT NULL DEFAULT ''"); + EnsureColumn(db, "problem_solutions", "tags_json", "tags_json TEXT NOT NULL DEFAULT '[]'"); + EnsureColumn(db, "redeem_items", "duration_minutes", + "duration_minutes INTEGER NOT NULL DEFAULT 0"); + + // Build indexes after compatibility ALTERs so old schemas won't fail on + // missing columns (e.g. legacy submissions table without contest_id). + db.Exec(R"SQL( +CREATE INDEX IF NOT EXISTS idx_submissions_user_created_at ON submissions(user_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_submissions_problem_created_at ON submissions(problem_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_submissions_contest_user_created_at ON submissions(contest_id, user_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_problem_tags_tag ON problem_tags(tag); +CREATE INDEX IF NOT EXISTS idx_kb_article_links_problem_id ON kb_article_links(problem_id); +CREATE INDEX IF NOT EXISTS idx_import_jobs_created_at ON import_jobs(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status, updated_at DESC); +CREATE INDEX IF NOT EXISTS idx_import_job_items_job_status ON import_job_items(job_id, status, updated_at DESC); +CREATE INDEX IF NOT EXISTS idx_problem_drafts_updated ON problem_drafts(updated_at DESC); +CREATE INDEX IF NOT EXISTS idx_problem_solution_jobs_problem ON problem_solution_jobs(problem_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_problem_solutions_problem ON problem_solutions(problem_id, variant, id); +CREATE INDEX IF NOT EXISTS idx_solution_view_logs_user_problem ON problem_solution_view_logs(user_id, problem_id, viewed_at DESC); +CREATE INDEX IF NOT EXISTS idx_solution_view_logs_user_day ON problem_solution_view_logs(user_id, day_key, viewed_at DESC); +CREATE INDEX IF NOT EXISTS idx_redeem_items_active ON redeem_items(is_active, id); +CREATE INDEX IF NOT EXISTS idx_redeem_records_user_created ON redeem_records(user_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_daily_task_logs_user_day ON daily_task_logs(user_id, day_key, created_at DESC); + )SQL"); +} + +void SeedDemoData(SqliteDb& db) { + sqlite3* raw = db.raw(); + const int64_t now = NowSec(); + + if (CountRows(raw, "problems") == 0) { + InsertProblem( + raw, + "a-plus-b", + "A + B", + "给定两个整数 A 与 B,输出它们的和。", + 1, + "CSP-入门", + "1 2\n", + "3\n", + now); + + InsertProblem( + raw, + "fibonacci-n", + "Fibonacci 第 n 项", + "输入 n (0<=n<=40),输出第 n 项 Fibonacci 数。", + 2, + "CSP-基础", + "10\n", + "55\n", + now); + + InsertProblem( + raw, + "sort-numbers", + "整数排序", + "输入 n 和 n 个整数,按升序输出。", + 2, + "CSP-基础", + "5\n5 1 4 2 3\n", + "1 2 3 4 5\n", + now); + } + + if (CountRows(raw, "problem_tags") == 0) { + const auto p1 = + QueryOneId(raw, "SELECT id FROM problems WHERE slug='a-plus-b'"); + const auto p2 = + QueryOneId(raw, "SELECT id FROM problems WHERE slug='fibonacci-n'"); + const auto p3 = + QueryOneId(raw, "SELECT id FROM problems WHERE slug='sort-numbers'"); + if (p1) { + InsertProblemTag(raw, *p1, "math"); + InsertProblemTag(raw, *p1, "implementation"); + } + if (p2) { + InsertProblemTag(raw, *p2, "dp"); + InsertProblemTag(raw, *p2, "recursion"); + } + if (p3) { + InsertProblemTag(raw, *p3, "sort"); + InsertProblemTag(raw, *p3, "array"); + } + } + + if (CountRows(raw, "kb_articles") == 0) { + InsertKbArticle( + raw, + "cpp-fast-io", + "C++ 快速输入输出", + "# C++ 快速输入输出\n\n在 OI/CSP 中,建议关闭同步并解绑 cin/cout:\n\n```cpp\nstd::ios::sync_with_stdio(false);\nstd::cin.tie(nullptr);\n```\n", + now); + + InsertKbArticle( + raw, + "intro-dp", + "动态规划入门", + "# 动态规划入门\n\n动态规划的核心是:**状态定义**、**状态转移**、**边界条件**。\n", + now); + } + + if (CountRows(raw, "kb_article_links") == 0) { + const auto p1 = + QueryOneId(raw, "SELECT id FROM problems WHERE slug='a-plus-b'"); + const auto p2 = + QueryOneId(raw, "SELECT id FROM problems WHERE slug='fibonacci-n'"); + const auto a1 = + QueryOneId(raw, "SELECT id FROM kb_articles WHERE slug='cpp-fast-io'"); + const auto a2 = + QueryOneId(raw, "SELECT id FROM kb_articles WHERE slug='intro-dp'"); + if (a1 && p1) InsertKbLink(raw, *a1, *p1); + if (a2 && p2) InsertKbLink(raw, *a2, *p2); + } + + if (CountRows(raw, "contests") == 0) { + InsertContest( + raw, + "CSP 模拟赛(示例)", + now - 3600, + now + 7 * 24 * 3600, + R"({"type":"acm","desc":"按通过题数与罚时排名"})"); + } + + if (CountRows(raw, "contest_problems") == 0) { + const auto contest_id = QueryOneId(raw, "SELECT id FROM contests ORDER BY id LIMIT 1"); + const auto p1 = QueryOneId(raw, "SELECT id FROM problems ORDER BY id LIMIT 1"); + const auto p2 = QueryOneId(raw, "SELECT id FROM problems ORDER BY id LIMIT 1 OFFSET 1"); + if (contest_id && p1) InsertContestProblem(raw, *contest_id, *p1, 1); + if (contest_id && p2) InsertContestProblem(raw, *contest_id, *p2, 2); + } + + if (CountRows(raw, "redeem_items") == 0) { + InsertRedeemItem( + raw, + "私人玩游戏时间", + "全局用户可兑换:假期 1 小时 5 Rating;学习日/非节假日 1 小时 25 Rating。", + "小时", + 5, + 25, + 1, + 1, + 0, + now); + } + + // Always seed C++基础课程任务(幂等:按 slug 检测存在)。 + { + const auto existing = QueryOneId(raw, "SELECT id FROM problems WHERE slug='cpp-basic-01-hello' LIMIT 1"); + if (!existing.has_value()) { + const int64_t created = now; + struct CourseItem { + const char* slug; + const char* title; + int diff; + const char* source; + const char* md; + const char* tags[6]; + }; + const CourseItem items[] = { + { + "cpp-basic-01-hello", + "C++基础01:环境配置与Hello World(VSCode)", + 1, + "course:cpp-basic:01", + R"MD(# C++基础01:环境配置与Hello World(VSCode) + +## 学习目标 +- 安装并打开 VSCode,创建并运行第一个 C++14 程序 +- 学会新建文件、保存、运行、查看输出 +- 了解 `main()`、`#include `、`cout` + +## 推荐视频(观看后写笔记) +- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq) + - https://www.bilibili.com/video/BV1tg411N7Fq/ +- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口) + - https://www.bilibili.com/video/BV1dK4y137bk/ + +## 参考图文 +- LoongBa 极简配置:GCC/VSCode/HelloWorld + - https://github.com/LoongBa/Cpp_Beginner_Guide + +## 练习(完成至少 2 题) +- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002 +- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000 + +## 提交要求 +- 在本题页面下方“学习笔记”区域写下: + 1) 你安装了什么、遇到什么坑、怎么解决 + 2) 你的 HelloWorld 代码 + 3) 你学到的 3 个关键词 +)MD", + {"cpp-basic", "vscode", "io", "", "", ""} + }, + { + "cpp-basic-02-io", + "C++基础02:输入输出与变量", + 1, + "course:cpp-basic:02", + R"MD(# C++基础02:输入输出与变量 + +## 学习目标 +- 会用 `cin` 读入、`cout` 输出 +- 理解变量:`int / long long / double / char / string` + +## 推荐视频 +- 每天五分钟学C++:02 输出、03 变量(系列入口见上) + - https://www.bilibili.com/video/BV1dK4y137bk/ + +## 练习 +- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001 +- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008 +- P5704 字母转换 https://www.luogu.com.cn/problem/P5704 + +## 提交要求 +- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行) +)MD", + {"cpp-basic", "io", "types", "", "", ""} + }, + { + "cpp-basic-03-branch", + "C++基础03:分支结构(if / switch)", + 2, + "course:cpp-basic:03", + R"MD(# C++基础03:分支结构(if / switch) + +## 学习目标 +- 会写 `if / else if / else` 与基本逻辑运算 +- 能处理边界与分类讨论 + +## 练习 +- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035 +- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711 +- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909 + +## 提交要求 +- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件) +)MD", + {"cpp-basic", "branch", "logic", "", "", ""} + }, + { + "cpp-basic-04-loop", + "C++基础04:循环结构(for / while)", + 2, + "course:cpp-basic:04", + R"MD(# C++基础04:循环结构(for / while) + +## 学习目标 +- 会用循环做:计数、累加、打印图形 + +## 练习 +- B2083 画矩形 https://www.luogu.com.cn/problem/B2083 +- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421 + +## 提交要求 +- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法 +)MD", + {"cpp-basic", "loop", "debug", "", "", ""} + }, + { + "cpp-basic-05-array", + "C++基础05:数组入门(一维)", + 3, + "course:cpp-basic:05", + R"MD(# C++基础05:数组入门(一维) + +## 学习目标 +- 会定义数组、遍历、统计 + +## 练习 +- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427 +- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428 + +## 提交要求 +- 笔记里写:数组下标从 0/1 的选择;如何避免越界 +)MD", + {"cpp-basic", "array", "", "", "", ""} + } + }; + + for (const auto& it : items) { + InsertProblem(raw, it.slug, it.title, it.md, it.diff, it.source, "", "", created); + const auto pid = QueryOneId(raw, std::string("SELECT id FROM problems WHERE slug='") + it.slug + "' LIMIT 1"); + if (pid.has_value()) { + for (const char* tag : it.tags) { + if (!tag || !*tag) continue; + InsertProblemTag(raw, *pid, tag); + } + } + } + } + } + +} + +} // namespace csp::db diff --git a/cpp_basic_20_upsert.sql b/cpp_basic_20_upsert.sql new file mode 100644 index 0000000..4cd6b15 --- /dev/null +++ b/cpp_basic_20_upsert.sql @@ -0,0 +1,363 @@ +BEGIN; +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-01-hello','C++基础01:环境配置与Hello World(VSCode)','# C++基础01:环境配置与Hello World(VSCode) + +## 学习目标 +- 安装并打开 VSCode,创建并运行第一个 C++14 程序 +- 学会新建文件、保存、运行、查看输出 +- 了解 `main()`、`#include `、`cout` + +## 推荐视频(观看后写笔记) +- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq) + - https://www.bilibili.com/video/BV1tg411N7Fq/ +- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口) + - https://www.bilibili.com/video/BV1dK4y137bk/ + +## 参考图文 +- LoongBa 极简配置:GCC/VSCode/HelloWorld + - https://github.com/LoongBa/Cpp_Beginner_Guide + +## 练习(完成至少 2 题) +- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002 +- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000 + +## 提交要求 +- 在本题页面下方“学习笔记”区域写下: + 1) 你安装了什么、遇到什么坑、怎么解决 + 2) 你的 HelloWorld 代码 + 3) 你学到的 3 个关键词 +',1,'course:cpp-basic:01',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-01-hello'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-01-hello'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-01-hello'),'vscode'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-01-hello'),'io'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-02-io','C++基础02:输入输出与变量','# C++基础02:输入输出与变量 + +## 学习目标 +- 会用 `cin` 读入、`cout` 输出 +- 理解变量:`int / long long / double / char / string` + +## 推荐视频 +- 每天五分钟学C++:02 输出、03 变量(系列入口见上) + - https://www.bilibili.com/video/BV1dK4y137bk/ + +## 练习 +- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001 +- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008 +- P5704 字母转换 https://www.luogu.com.cn/problem/P5704 + +## 提交要求 +- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行) +',1,'course:cpp-basic:02',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-02-io'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-02-io'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-02-io'),'io'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-02-io'),'types'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-03-branch','C++基础03:分支结构(if / switch)','# C++基础03:分支结构(if / switch) + +## 学习目标 +- 会写 `if / else if / else` 与基本逻辑运算 +- 能处理边界与分类讨论 + +## 练习 +- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035 +- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711 +- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909 + +## 提交要求 +- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件) +',2,'course:cpp-basic:03',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-03-branch'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-03-branch'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-03-branch'),'branch'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-03-branch'),'logic'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-04-loop','C++基础04:循环结构(for / while)','# C++基础04:循环结构(for / while) + +## 学习目标 +- 会用循环做:计数、累加、打印图形 + +## 练习 +- B2083 画矩形 https://www.luogu.com.cn/problem/B2083 +- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421 + +## 提交要求 +- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法 +',2,'course:cpp-basic:04',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-04-loop'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-04-loop'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-04-loop'),'loop'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-04-loop'),'debug'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-05-array','C++基础05:数组入门(一维)','# C++基础05:数组入门(一维) + +## 学习目标 +- 会定义数组、遍历、统计 + +## 练习 +- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427 +- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428 + +## 提交要求 +- 笔记里写:数组下标从 0/1 的选择;如何避免越界 +',3,'course:cpp-basic:05',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-05-array'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-05-array'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-05-array'),'array'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-06-char-string','C++基础06:字符与字符串(char / string)','# C++基础06:字符与字符串(char / string) + +## 学习目标 +- 区分 `char`(一个字符)与 `string`(一串字符) +- 会做:大小写转换、统计字母、简单加密 + +## 重点 +- `char c=''A''`,`string s="abc"` +- `s.size()` / `s[i]` / 遍历字符串 + +## 练习(完成至少 2 题) +- P5704 字母转换 https://www.luogu.com.cn/problem/P5704 +- P5733 自动修正 https://www.luogu.com.cn/problem/P5733 +- P1914 小书童——凯撒密码 https://www.luogu.com.cn/problem/P1914 + +## 提交要求 +- 笔记写:`string` 下标访问与越界风险(最后一个下标是 size()-1) +',3,'course:cpp-basic:06',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-06-char-string'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-06-char-string'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-06-char-string'),'string'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-06-char-string'),'char'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-07-operator-cast','C++基础07:运算、取整与类型转换(/ %)','# C++基础07:运算、取整与类型转换(/ %) + +## 学习目标 +- 写对 `+ - * / %` +- 理解:整数除法会向下取整;`%` 只能用于整数 +- 学会用 `long long` 防止溢出 + +## 练习 +- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008 +- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421 +- P5708 三角形面积 https://www.luogu.com.cn/problem/P5708 + +## 提交要求 +- 笔记写:什么时候要用 `long long`(例如钱、人数、乘法很大) +',3,'course:cpp-basic:07',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-07-operator-cast'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-07-operator-cast'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-07-operator-cast'),'math'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-07-operator-cast'),'cast'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-08-logic','C++基础08:逻辑运算与边界(&& || !)','# C++基础08:逻辑运算与边界(&& || !) + +## 学习目标 +- 能把题目里的“并且/或者/不是”写成条件 +- 学会找边界:等于、最小/最大、0 + +## 练习 +- P5710 数的性质 https://www.luogu.com.cn/problem/P5710 +- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711 +- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909 + +## 提交要求 +- 笔记写:你列出的边界测试用例(至少 3 个) +',3,'course:cpp-basic:08',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-08-logic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-08-logic'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-08-logic'),'logic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-08-logic'),'boundary'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-09-switch','C++基础09:switch 与枚举列情况','# C++基础09:switch 与枚举列情况 + +## 学习目标 +- 会写 `switch/case/break` +- 面对“情况很少”的题,会列举所有情况 + +## 练习 +- P2433 小学数学N合一 https://www.luogu.com.cn/problem/P2433 + +## 提交要求 +- 笔记写:为什么每个 case 后要 `break`(避免贯穿执行) +',3,'course:cpp-basic:09',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-09-switch'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-09-switch'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-09-switch'),'switch'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-09-switch'),'enum'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-10-for','C++基础10:for循环(计数与累加)','# C++基础10:for循环(计数与累加) + +## 学习目标 +- 熟练掌握 `for(初始化; 条件; 更新)` +- 会写:求和、求最大最小、统计个数 + +## 练习 +- P1425 小鱼的游泳时间 https://www.luogu.com.cn/problem/P1425 +- P5718 找最小值 https://www.luogu.com.cn/problem/P5718 + +## 提交要求 +- 笔记写:循环“三要素”分别是什么 +',3,'course:cpp-basic:10',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-10-for'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-10-for'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-10-for'),'loop'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-10-for'),'for'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-11-while','C++基础11:while循环(未知次数)','# C++基础11:while循环(未知次数) + +## 学习目标 +- 会用 `while` 处理:读到结束、读到 0 停止 +- 避免死循环:每次循环必须改变状态 + +## 练习 +- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427 +- P1047 校门外的树(选做)https://www.luogu.com.cn/problem/P1047 + +## 提交要求 +- 笔记写:你是如何避免死循环的(更新变量写在哪里) +',3,'course:cpp-basic:11',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-11-while'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-11-while'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-11-while'),'loop'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-11-while'),'while'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-12-nested','C++基础12:嵌套循环与打印图形','# C++基础12:嵌套循环与打印图形 + +## 学习目标 +- 外层循环=行,内层循环=列 +- 能打印:矩形、三角形、乘法表 + +## 练习 +- B2083 画矩形 https://www.luogu.com.cn/problem/B2083 +- P5717 三角形分类(练分类讨论 + 输出格式)https://www.luogu.com.cn/problem/P5717 + +## 提交要求 +- 笔记写:你画图时是怎么想“行/列”的 +',3,'course:cpp-basic:12',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-12-nested'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-12-nested'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-12-nested'),'nested-loop'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-12-nested'),'print'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-13-array-ops','C++基础13:数组进阶(统计/查找/反转)','# C++基础13:数组进阶(统计/查找/反转) + +## 学习目标 +- 用数组做:统计、查找位置、反转、去重(了解) + +## 练习 +- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428 +- P5727 冰雹猜想 https://www.luogu.com.cn/problem/P5727 + +## 提交要求 +- 笔记写:数组越界的 2 个典型原因 + 你的检查方法 +',4,'course:cpp-basic:13',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-13-array-ops'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-13-array-ops'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-13-array-ops'),'array'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-13-array-ops'),'practice'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-14-function','C++基础14:函数入门(把重复代码变成工具)','# C++基础14:函数入门(参数/返回值/作用域) + +## 学习目标 +- 会定义并调用函数 +- 知道局部变量作用域({} 内有效) + +## 练习 +- P5735 距离函数 https://www.luogu.com.cn/problem/P5735 +- P5739 计算阶乘(也可用递归)https://www.luogu.com.cn/problem/P5739 + +## 提交要求 +- 笔记写:你把哪段重复代码抽成了函数?为什么这样更清晰? +',4,'course:cpp-basic:14',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-14-function'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-14-function'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-14-function'),'function'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-14-function'),'scope'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-15-recursion','C++基础15:递归入门(选学)','# C++基础15:递归入门(选学) + +## 学习目标 +- 理解递归:函数调用自己 +- 必须有终止条件(否则会无限调用) + +## 练习 +- P5739 计算阶乘 https://www.luogu.com.cn/problem/P5739 +- P1028 数的计算(选做)https://www.luogu.com.cn/problem/P1028 + +## 提交要求 +- 笔记写:你的递归“终止条件”是什么? +',4,'course:cpp-basic:15',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-15-recursion'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-15-recursion'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-15-recursion'),'recursion'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-16-struct','C++基础16:结构体 struct(选学)','# C++基础16:结构体 struct(选学) + +## 学习目标 +- 用 `struct` 把多个字段打包成一个“人物卡/记录” +- 学会 `struct` 数组 + +## 练习 +- P5740 最厉害的学生 https://www.luogu.com.cn/problem/P5740 +- P5744 培训 https://www.luogu.com.cn/problem/P5744 + +## 提交要求 +- 笔记写:struct 适合用在什么场景(多个属性属于同一个对象) +',4,'course:cpp-basic:16',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-16-struct'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-16-struct'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-16-struct'),'struct'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-17-sort','C++基础17:排序初步(sort)','# C++基础17:排序初步(sort) + +## 学习目标 +- 会用 `#include ` 和 `sort` +- 排序后做统计/查找会更简单 + +## 练习 +- P1059 明明的随机数 https://www.luogu.com.cn/problem/P1059 +- P1093 奖学金(选做)https://www.luogu.com.cn/problem/P1093 + +## 提交要求 +- 笔记写:sort 的区间是 `[begin, end)`(end 不包含) +',4,'course:cpp-basic:17',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-17-sort'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-17-sort'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-17-sort'),'sort'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-17-sort'),'algorithm'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-18-binary-search','C++基础18:二分查找初步(lower_bound)','# C++基础18:二分查找初步(lower_bound) + +## 学习目标 +- 理解“在有序数组里快速找答案” +- 会用 `lower_bound` 找第一个 >= x 的位置 + +## 练习 +- P2249 查找 https://www.luogu.com.cn/problem/P2249 + +## 提交要求 +- 笔记写:二分查找的前提:数组必须有序 +',4,'course:cpp-basic:18',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-18-binary-search'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-18-binary-search'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-18-binary-search'),'binary-search'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-19-prefix-sum','C++基础19:前缀和(区间求和神器)','# C++基础19:前缀和(区间求和神器) + +## 学习目标 +- 会构造前缀和 `s[i]=s[i-1]+a[i]` +- 会算区间和 `sum(l,r)=s[r]-s[l-1]` + +## 练习 +- P8218 求区间和(前缀和基础)https://www.luogu.com.cn/problem/P8218 + +## 提交要求 +- 笔记写:下标偏移是怎么处理的(从 1 开始更方便) +',4,'course:cpp-basic:19',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-19-prefix-sum'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-19-prefix-sum'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-19-prefix-sum'),'prefix-sum'); +INSERT INTO problems(slug,title,statement_md,difficulty,source,created_at) VALUES('cpp-basic-20-simulation','C++基础20:模拟题与综合复盘','# C++基础20:模拟题与综合复盘 + +## 学习目标 +- 学会按题意一步步做(模拟) +- 复盘:总结常见错误 + 常用模板 + +## 练习(从这里选 3~5 题做小测) +- P1957 口算练习题 https://www.luogu.com.cn/problem/P1957 +- P1055 ISBN号码 https://www.luogu.com.cn/problem/P1055 +- P2433 小学数学N合一(回顾)https://www.luogu.com.cn/problem/P2433 + +## 提交要求 +- 笔记写: + 1) 你最常犯的 3 类错误 + 2) 你最常用的 5 行代码模板 + 3) 下周你准备怎么练 +',4,'course:cpp-basic:20',1771241843) ON CONFLICT(slug) DO UPDATE SET title=excluded.title, statement_md=excluded.statement_md, difficulty=excluded.difficulty, source=excluded.source; +DELETE FROM problem_tags WHERE problem_id=(SELECT id FROM problems WHERE slug='cpp-basic-20-simulation'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-20-simulation'),'cpp-basic'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-20-simulation'),'simulation'); +INSERT OR IGNORE INTO problem_tags(problem_id,tag) VALUES((SELECT id FROM problems WHERE slug='cpp-basic-20-simulation'),'review'); +COMMIT; diff --git a/frontend/src/app/problems/[id]/page.tsx b/frontend/src/app/problems/[id]/page.tsx index eacd94a..170efc8 100644 --- a/frontend/src/app/problems/[id]/page.tsx +++ b/frontend/src/app/problems/[id]/page.tsx @@ -950,14 +950,18 @@ export default function ProblemDetailPage() { const printProblemWithAnswer = async () => { let resolved = solutionData; - if (resolved?.access?.mode !== "full") { - const fetched = await loadSolutions("full"); - if (fetched) resolved = fetched; - } else if ((resolved?.items.length ?? 0) === 0) { + // If user has already unlocked solutions (full mode), use them directly + if (resolved?.access?.mode === "full" && (resolved?.items.length ?? 0) > 0) { + // Already have full solutions loaded — use as-is + } else if (resolved?.access?.mode === "full") { + // Full access but items not loaded yet — fetch const fetched = await loadSolutions("full"); if (fetched) resolved = fetched; } - setPrintAnswerMarkdown(buildPrintableAnswerMarkdown(resolved, tx)); + // If not in full mode, just print problem without answer + setPrintAnswerMarkdown( + resolved?.access?.mode === "full" ? buildPrintableAnswerMarkdown(resolved, tx) : "" + ); await new Promise((resolve) => { window.requestAnimationFrame(() => { window.requestAnimationFrame(() => resolve()); @@ -997,7 +1001,11 @@ export default function ProblemDetailPage() { onClick={() => void printProblemWithAnswer()} disabled={solutionLoading} > - {solutionLoading ? tx("卷轴准备中...", "Scribing...") : tx("打印卷轴", "Scribe Scroll")} + {solutionLoading + ? tx("卷轴准备中...", "Scribing...") + : solutionData?.access?.mode === "full" + ? tx("🖨️ 打印题目+答案", "🖨️ Print with Answer") + : tx("🖨️ 打印题目", "🖨️ Print Problem")}