CSP-J 模拟赛1 讲评
CSP-J 普及模拟赛(一)—— 讲评课件
第一部分:CSP-J 复赛比赛策略
一、江西省一等奖分数线回顾
| 年份 | CSP-J 江西省一等奖分数线 | 全国基准线 |
|---|---|---|
| 2022 | 165 分 | — |
| 2023 | 170 分 | — |
| 2024 | 230 分 | 250 分 |
| 2025 | 235 分 | 255 分 |
趋势分析:分数线逐年攀升,2024 年出现大幅跳涨(+60 分),2025 年稳定在 235 分左右。按照此趋势,目标分数应设定在 250 分以上才比较稳妥。
二、核心得分策略:保二争三望四
CSP-J 复赛共 4 题,每题 100 分,满分 400 分。要达到 250 分的目标:
| 题目 | 目标得分 | 累计分数 | 说明 |
|---|---|---|---|
| T1 | 100 分(满分) | 100 | 必须拿下,难度最低 |
| T2 | 100 分(满分) | 200 | 必须拿下,思维为主 |
| T3 | 30~50 分 | 230~250 | 部分分是关键 |
| T4 | 20~30 分 | 250~280 | 保底部分分即可 |
| 合计 | 250~280 分 | — | 稳稳过线 |
核心原则:T1、T2 必须满分! 这是省一的基础。T1 通常是模拟/简单数学,T2 通常是思维/构造/贪心。这两题丢分,基本无缘省一。
三、时间分配策略(考试 3.5 小时 = 210 分钟)
考试开始 ──────────────────────────────────────────────> 考试结束
│ │
│ 0~30min 30~90min 90~150min 150~200min 200~210min
│ ├──────────┼───────────┼────────────┼───────────┼──────────┤
│ │ 读题+ │ T1,T2 │ T3,T4 │ 检查+ │ 最终 │
│ │ 通览 │ 正解 │ 部分分 │ 补刀 │ 检查 │
| 时间段 | 时长 | 任务 | 具体做法 |
|---|---|---|---|
| 第 1 阶段 | 0~30 min | 通读四道题 | 快速读完所有题目,标记每题的难度感觉和数据范围,心里有个底 |
| 第 2 阶段 | 30~90 min | 死磕 T1、T2 正解 | 先做 T1,稳定拿满 100 分。再做 T2,仔细推敲边界条件,保证 AC |
| 第 3 阶段 | 90~150 min | T3、T4 部分分 | 先快速拿到 T3 的暴力/特殊性质分。再攻 T4 的暴力分。如果 T3 有思路就深入 |
| 第 4 阶段 | 150~200 min | 查漏补缺 | 检查 T1、T2 有无 bug。尝试在 T3 或 T4 上多拿分(打表、特殊 case、更优的部分分) |
| 第 5 阶段 | 200~210 min | 最终检查 | 检查文件名、输入输出、数组大小、long long、清零、freopen 等 |
四、考场上的决策要点
1. 读完题后快速判断
- 看到 \(n \le 10^5\):通常是 \(O(n \log n)\) 或 \(O(n)\) 算法
- 看到 \(n \le 1000\) 且大样例范围:可能是 \(O(n^2)\) 拿部分分
- 看到"字典序最小":通常涉及贪心或 BFS 分层
- 看到值域很小(如 \(|a_i| \le 1000\)):可以考虑桶计数
2. T1/T2 拿满分的纪律
- 写代码前先用纸笔验证样例,确认思路无误
- 注意数据范围:该开
long long的地方一定要开 - 注意取模(虽然本场没有):看清楚题目是否有模数
- 造边界数据测试:最小值、最大值、0、负数等
3. T3/T4 拿部分分的技巧
- 先看数据范围的分档,确定自己能拿到哪一档
- 暴力也要写对:\(O(n^2)\) 暴力拿 30 分,比花 1 小时推不出正解拿 0 分强
- 特殊性质单独处理:如 \(k=0\)、\(n\) 很小等
- 打表:如果输入范围很小,可以本地跑出所有答案
- 输出随机/固定值赌运气:实在不会做时不得已的选择
4. 避免常见错误
- 文件名写错(大小写、拼写)
freopen忘写或写错- 数组开小(RE)
- 没开
long long(WA) - 多测不清空(WA)
- 死循环/递归爆栈(TLE/RE)
第二部分:赛题讲评
T1:评估(assess)—— 必须满分
题目大意:给定长度为 \(n\) 的数列 \(a_i\),求 \(\sum_{i=1}^{n-1} \sum_{j=i+1}^n |a_i - a_j|^2\)。
站在赛场上的思考过程
第一步:读题,判断难度
看到求所有数对之差的平方和。数据范围 \(n \le 10^5\),\(|a_i| \le 1000\)。—— 注意到值域非常小!这提示我们可能有两种做法。
第二步:先想暴力,确保有分
最简单的想法:两层循环枚举所有 \((i, j)\) 对,直接计算。时间复杂度 \(O(n^2)\),当 \(n \le 1000\) 时可以拿 40 分。
部分分(40%):\(O(n^2)\) 暴力枚举。
第三步:思考正解
关键观察:平方可以去掉绝对值!\(|a_i - a_j|^2 = (a_i - a_j)^2\)。
展开式子:
现在把整个求和拆成两部分:
前半部分:所有数对的 \(a_i^2 + a_j^2\) 之和。
换个角度想——我们不是在枚举数对,而是在数每个元素被用了多少次。
以 \(n=3\) 为例,有三对数对:\((1,2)\)、\((1,3)\)、\((2,3)\)。
- \(a_1^2\) 出现在数对 \((1,2)\) 和 \((1,3)\) 中,共 \(2\) 次
- \(a_2^2\) 出现在数对 \((1,2)\) 和 \((2,3)\) 中,共 \(2\) 次
- \(a_3^2\) 出现在数对 \((1,3)\) 和 \((2,3)\) 中,共 \(2\) 次
发现规律:每个 \(a_i^2\) 恰好出现了 \(n-1\) 次(因为它要跟除自己以外的每个元素各配一次对)。
所以不需要两层循环,直接:
一次遍历算出所有 \(a_i^2\) 的和,乘上 \(n-1\) 即可,复杂度 \(O(n)\)。
后半部分:所有数对的 \(-2 \times a_i \times a_j\) 之和。
同样用 \(n=3\) 来理解,我们需要算:
提取公因数,把含 \(a_1\) 的项放一起:
看到了吗?对于每个 \(a_i\),它要乘以排在它后面的所有 \(a_j\) 之和:
- \(a_1\) 乘以 \((a_2 + a_3)\) —— 即 \(a_1\) 之后所有数的和
- \(a_2\) 乘以 \(a_3\) —— 即 \(a_2\) 之后所有数的和
所以只需要一个后缀和数组 \(suf[i]\) = "从第 \(i\) 个位置到末尾的所有数之和",然后一次遍历:
同样是 \(O(n)\)。
正解思路:
- 预处理后缀和 \(suf[i] = \sum_{j=i}^n a_j\)
- 遍历 \(i\) 从 \(1\) 到 \(n-1\),累加 \(-2 \times a_i \times suf[i+1]\)
- 再加上 \(\sum a_i^2 \times (n-1)\)
- 总时间复杂度 \(O(n)\)
另一种正解思路(利用值域小)
注意题目的数据范围:\(n\) 可以到 \(10^5\),但 \(|a_i| \le 1000\)——值的种类很少,大量元素是重复的!
我们换一个角度:不再按位置枚举数对,而是按数值枚举数对。
举个例子,假设数列是 \([2, 8, 2, 8, 2]\):
- 值 \(2\) 出现了 \(3\) 次,值 \(8\) 出现了 \(2\) 次
- 一个 \(2\) 和一个 \(8\) 组成的数对有 \(3 \times 2 = 6\) 对,每对贡献 \((2-8)^2 = 36\)
- 所以值 \(2\) 和值 \(8\) 之间总共贡献 \(36 \times 6 = 216\)
一般化:用桶 \(cnt[x]\) 记录数值 \(x\) 出现了多少次,然后枚举所有不同的数值对 \((x, y)\)(\(x < y\)):
复杂度 \(O(m^2)\),其中 \(m\) 是值域大小(约 \(2000\)),\(m^2 \approx 4 \times 10^6\),轻松通过。
对比:枚举位置需要 \(O(n^2)\)(\(n=10^5\) 时约 \(10^{10}\),必超时),枚举数值只需要 \(O(m^2)\)(约 \(4 \times 10^6\),很快)。这就是值域小带来的红利。
关键要点
- 去掉绝对值:平方运算让绝对值形同虚设
- 数学化简:将双重求和拆成独立的部分
- 预处理优化:前缀和/后缀和是处理"区间求和"的利器
- 注意开
long long:结果可能很大(\(n\) 可达 \(10^5\),\(a_i\) 可达 \(1000\),平方后更大)
T2:拆分数字(split)—— 必须满分
题目大意:判断能否用恰好 \(k\) 个 \(3\) 的幂次(\(3^0, 3^1, 3^2, \ldots\))相加得到 \(n\)。
站在赛场上的思考过程
第一步:理解题意
我们要把 \(n\) 拆成恰好 \(k\) 个数的和,每个数都必须是 \(3^m\) 的形式(\(m \ge 0\))。同一种幂次可以重复使用。
第二步:先分析最小个数和最大个数
-
最大个数:把 \(n\) 全部拆成 \(3^0 = 1\) 相加,需要 \(n\) 个数。
-
最小个数:把 \(n\) 写成三进制,每一位上的数字之和就是最少需要的 \(3^m\) 的个数。
例如 \(n = 5\),三进制为 \((12)_3\),即 \(5 = 1 \times 3^1 + 2 \times 3^0\),最少需要 \(1+2=3\) 个数。
记 \(n\) 的三进制表示的各位数字之和为 \(m\)(即最少需要的个数)。
第三步:判断可行性
- 如果 \(k < m\):不可能,因为 \(m\) 是最少需要的个数。
- 如果 \(k = m\):可行,直接用三进制展开即可。
- 如果 \(m < k \le n\):我们可以通过"拆分"操作增加个数。
第四步:"拆分"操作的本质
将一个 \(3^i\) 拆成三个 \(3^{i-1}\):
原来 1 个数,现在变成 3 个数——净增加 2 个。
这说明:每次拆分操作,个数增加 2。所以从 \(m\) 开始,能得到的个数是:
第五步:得出结论
- 首先需要 \(m \le k \le n\)(最少不超过 \(k\),最多不超过 \(n\))
- \(k\) 和 \(m\) 的奇偶性必须相同(因为每次增加 2)
满足以上两个条件即为 Yes,否则为 No。
正解核心:
- 计算 \(n\) 的三进制数位之和 \(m\)
- 判断 \(m \le k \le n\) 且 \(m\) 与 \(k\) 同奇偶
部分分分析
| 数据范围 | 做法 | 分数 |
|---|---|---|
| \(n \le 10, k \le 5\) | DFS 枚举每个位置选哪个幂次 | 30% |
| \(n \le 1000, k \le 2\) | 枚举两个幂次是否相加等于 \(n\) | 另 30% |
注意:\(T\) 可达 \(10^5\),每组询问必须 \(O(1)\) 或 \(O(\log n)\) 回答。
关键要点
- 发现"拆分"操作每次增加 2 是核心突破口
- 三进制的数位和 = 最少拆分个数,这是三进制的重要性质
- 奇偶性判断是最终判定条件的关键一环
- 多组询问注意用
scanf/printf或关闭同步
T3:露营(camp)—— 侧重部分分
题目大意:平面上三个点 \(A, B, C\),求最少清理多少个小方格,使得三个点通过相邻(有公共边)的已清理方格连通。
一、题目到底在问什么?
想象你有一张方格纸,上面标了三个点 A、B、C。你可以"清理"某些格子(把它们标记为可用)。"连通"的意思是:清理出的格子边挨着边,形成一条或多条通道,让你能从任意一个点走到另外两个点。
🏕️ 打个比方:三个小朋友在方格地图上的不同位置,你要用最少的方砖铺路,让他们能互相走到对方的位置。砖只能横着或竖着相邻铺(不能斜着),问最少铺多少块砖。
连接三个点可以有不同的形状,比如排成一串(链条形),或者在中间某个位置分叉汇合(Y 字形):
二、从最简单的说起:一维情况(三个点在同一条线上)
如果三个点恰好在同一行(y 坐标相同),那它们就像数轴上的三个数,排成一排:
A(x=0) ─── B(x=2) ─── C(x=5)
0 1 2 3 4 5
要把它们连起来,显然把从最左到最右的所有格子都清理掉就行了:
比如上面:\(5 - 0 + 1 = 6\) 个格子。
同一列的情况完全一样,只是把 x 换成 y。这是送分的特判——如果你发现三个点共线,直接套公式。
三、进入二维:两个点怎么连?
在讲三个点之前,先看两个点。在方格纸上从点 P 走到点 Q,只能横着或竖着走(不能斜着)。最短路径有好多条,但路径上的格子数是固定的:
前面的 \(|x_P - x_Q| + |y_P - y_Q|\) 就是曼哈顿距离(步数),最后 +1 是因为起点格子也要算进去。
比如从 (0,0) 走到 (2,1),曼哈顿距离 = 2 + 1 = 3 步,经过 4 个格子:
(0,0) ─ (1,0) ─ (2,0)
│
(2,1)
路径像一个 L 形——先横着走,再竖着走(或者反过来),拐一个弯就到。
💡 关键认识:两点之间,路径格子数 = 横向跨度 + 纵向跨度 + 1。
四、三个点怎么连?—— 找一个"集合点"
现在回到三个点。最自然的想法是:三条路径在某个格子碰头,像一个"Y"字形的路口。
就像三个朋友约定在某个地方集合,每人各自走过去。这个碰头的地方我们叫它"汇合点"。
核心问题变成:汇合点选在哪里,总砖数最少?
五、用一个具体例子手推
以样例 2 为例:A = (0,0),B = (2,0),C = (1,1)。画在方格纸上:
y=1: C(1,1)
│
y=0: A(0,0) ─── ■(1,0) ─── B(2,0)
x=0 x=1 x=2
怎么用最少格子把它们连起来?
- 先铺一条横贯左右的主干道:在 y=0 这行,从最左的 (0,0) 到最右的 (2,0),共 \(2-0+1 = 3\) 个格子(A、■、B 都在上面了)
- C 点竖着走到主干道:从 (1,1) 走到 (1,0),但 (1,0) 已经铺过了,所以只新增 1 个格子((1,1) 本身)
- 总共 = 3 + 1 = 4 格 ✓
看,共用格子是关键!主干道上的 ■(1,0) 被两条路径共享,不用重复算。
六、推广:通用构造法
不管三个点怎么分布,都可以照猫画虎:
步骤 ①:画出恰好框住三个点的最小矩形
┌─────────────────────┐
│ │
│ 三个点都在这个框里 │
│ │
└─────────────────────┘
宽度 = x_max - x_min
高度 = y_max - y_min
步骤 ②:在矩形的"中间高度",画一条从左到右的横线(主干道)
横线格子数 = 宽度 + 1
步骤 ③:从上方的点向下画竖线接到主干道,从下方的点向上画竖线接到主干道
竖线新增格子数 = 高度(因为主干道的格子已经被算过了)
算一算总账:
- 横线(主干道):\(x_{max} - x_{min} + 1\) 格
- 上方的竖线:\(y_{max} - y_{mid}\) 格(起点在主干道上,不重复算)
- 下方的竖线:\(y_{mid} - y_{min}\) 格(同理)
- 加起来,中间的 \(y_{mid}\) 刚好消掉:
七、正解:一行公式
答案 = (最大的 x − 最小的 x) + (最大的 y − 最小的 y) + 1
更直观的记忆方式:
📐 答案 = 包围矩形的宽度 + 包围矩形的高度 + 1
用样例验证:
| 样例 | 三个点 | 宽度 | 高度 | 答案 | 正确? |
|---|---|---|---|---|---|
| 样例 1 | (0,0), (1,1), (2,2) | 2-0=2 | 2-0=2 | 2+2+1=5 | ✓ |
| 样例 2 | (0,0), (2,0), (1,1) | 2-0=2 | 1-0=1 | 2+1+1=4 | ✓ |
八、直观理解:为什么这么简单?
想象你拿一支笔画图:
- 从左到右画一条贯穿全部 x 范围的横线 → 宽 + 1 格
- 再从上到下补竖线,因为横线已经占了一整行 → 竖线只需补 高 格
最终形状像一个侧躺的 "H"(⊢):中间一横贯穿左右,竖线只延伸到上下的端点。三条路径在横线上自然汇合,没有一格被浪费。
这也解释了为什么选 y 坐标的"中间值"来画主干道——这样上下两段竖线各走各的,不会重叠,格子利用率最高。
九、考场上怎么拿分?
| 你在哪个阶段 | 怎么做 | 能拿多少分 |
|---|---|---|
| 完全没思路 | 暴力枚举所有可能的清理方案(3×3 只有 \(2^9=512\) 种),BFS/DFS 检查连通性 | 30 分 |
| 发现共线特判 | 三个点同行或同列时输出 max - min + 1 |
额外特殊性质分 |
| 猜到规律 | 手画几个例子找数字规律,猜出公式后用样例验证 | 100 分 |
💡 赛场智慧:几何题的正解往往短得离谱。如果推不出来,先写暴力保住 30 分,然后在草稿纸上多画几个不同位置的三点图,数一数答案,看看数字之间有没有固定关系。很多选手的正解就是"猜"出来的——猜到了就血赚!
关键要点
- 一维是二维的基础:共线时答案 =
max - min + 1,这是公式的特例 - 共用格子是省格子的关键:主干道被多条路径共享,只算一次
- 多画图:几何题不要光盯着屏幕,拿笔画几个例子,规律一目了然
- 正解只有一行:
(maxX - minX) + (maxY - minY) + 1,关键是敢猜到并验证
T4:寻宝(treasure)
简化题意
给定一个 (n\times n) 的字母矩阵。从左上角 ((0,0)) 走到右下角 ((n-1,n-1)),每次只能向右或向下,路径固定长度为 (2n-1)。你可以将路径上的 至多 (k) 个格子 改为任意小写字母,目标是使最终路径的字符串字典序最小,输出这个字符串。
部分分解法(完整代码)
1. 子任务:(n \le 5)(暴力枚举所有路径)
当 (n \le 5) 时,路径总数最多 (\binom{8}{4}=70),可以 DFS 枚举所有路径。
对每条路径得到的原字符串,贪心地从左到右扫描,如果当前字符不是 'a' 且还有剩余修改次数,就把它改成 'a',这样得到的字符串就是该路径能改出的最小字符串。最后取所有路径中的最小者。
#include <bits/stdc++.h>
using namespace std;
int n, k;
vector<string> grid;
string best = "~"; // 比任何小写字符串都大
// 当前在 (x,y),已经走过的路径为 path(不含当前位置)
void dfs(int x, int y, string path) {
path += grid[x][y]; // 加入当前格子
if (x == n - 1 && y == n - 1) {
// 对该路径进行贪心修改
string cur = path;
int rem = k;
for (int i = 0; i < cur.size() && rem > 0; i++) {
if (cur[i] != 'a') {
cur[i] = 'a';
rem--;
}
}
if (cur < best) best = cur;
return;
}
if (x + 1 < n) dfs(x + 1, y, path);
if (y + 1 < n) dfs(x, y + 1, path);
}
int main() {
cin >> n >> k;
grid.resize(n);
for (int i = 0; i < n; i++) cin >> grid[i];
dfs(0, 0, "");
cout << best << '\n';
return 0;
}
2. 子任务:(k=0)(不能修改,逐层贪心)
当不能修改时,只需要在原矩阵上找字典序最小的路径。
采用逐层贪心:从起点开始,每一步选择当前可达格子中字母最小的那些,并扩展它们的后继。由于不能修改,当前字母必须等于原字母。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<string> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
// 只处理 k == 0 的情况
vector<char> cur(n, 0); // cur[i] = 1 表示 (i, t-i) 可达
cur[0] = 1;
string ans;
for (int t = 0; t < 2 * n - 1; t++) {
int l = max(0, t - (n - 1));
int r = min(n - 1, t);
for (char c = 'a'; c <= 'z'; c++) {
vector<char> nxt(n, 0);
bool ok = false;
for (int i = l; i <= r; i++) {
if (!cur[i]) continue;
int j = t - i;
if (a[i][j] != c) continue; // k=0 不能改
if (t == 2 * n - 2) { // 到达终点
ok = true;
break;
}
// 向下
if (i + 1 < n) { nxt[i + 1] = 1; ok = true; }
// 向右
if (j + 1 < n) { nxt[i] = 1; ok = true; }
}
if (ok) {
ans.push_back(c);
if (t < 2 * n - 2) {
for (int i = 0; i < n; i++) cur[i] = nxt[i];
}
break;
}
}
}
cout << ans << '\n';
return 0;
}
正解(满分算法,(n \le 1000, 0 \le k \le n^2))
使用 逐层贪心 + DP。
设当前已确定答案的前 (t) 个字符,当前层满足 (i+j=t)。用 (dp[i]) 表示到达格子 ((i, t-i)) 且当前位已确定时,最少使用了多少次修改。
对每个位置,依次尝试字母 'a' 到 'z',如果存在一条路径使当前位变成该字母且总修改次数 (\le k),则选择该字母,并更新下一层的 DP 值。
复杂度 (O(26 \cdot n^2)),可通过。
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<string> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
int m = 2 * n - 1; // 路径长度
vector<int> dp(n, INF), ndp(n, INF);
dp[0] = 0; // 起点,尚未确定任何字符
string ans;
for (int t = 0; t < m; t++) {
int l = max(0, t - (n - 1));
int r = min(n - 1, t);
int choose = -1;
for (int c = 0; c < 26; c++) {
int nl = max(0, t + 1 - (n - 1));
int nr = min(n - 1, t + 1);
if (t + 1 < m) {
for (int i = nl; i <= nr; i++) ndp[i] = INF;
}
bool can = false;
for (int i = l; i <= r; i++) {
if (dp[i] > k) continue;
int j = t - i;
int add = (a[i][j] - 'a' == c ? 0 : 1);
int cost = dp[i] + add;
if (cost > k) continue;
if (t == m - 1) {
can = true;
break;
}
// 向下
if (i + 1 < n) {
ndp[i + 1] = min(ndp[i + 1], cost);
}
// 向右
if (j + 1 < n) {
ndp[i] = min(ndp[i], cost);
}
}
bool ok = false;
if (t == m - 1) {
ok = can;
} else {
for (int i = nl; i <= nr; i++) {
if (ndp[i] != INF) { ok = true; break; }
}
}
if (ok) {
choose = c;
break;
}
}
ans.push_back(char('a' + choose));
if (t + 1 < m) {
int nl = max(0, t + 1 - (n - 1));
int nr = min(n - 1, t + 1);
for (int i = nl; i <= nr; i++) {
dp[i] = ndp[i];
}
}
}
cout << ans << '\n';
return 0;
}
第三部分:本场模拟赛总结
分数目标复盘
| 题目 | 目标 | 难度 | 关键知识点 |
|---|---|---|---|
| T1 评估 | 100 分 | ★★☆ | 数学化简、前缀和、值域桶 |
| T2 拆分数字 | 100 分 | ★★☆ | 三进制性质、奇偶性、构造思维 |
| T3 露营 | 30~50 分 | ★★★ | 贪心、几何直觉、曼哈顿距离 |
| T4 寻宝 | 20~30 分 | ★★★★ | DP + 分层 BFS、字典序 |
理想总分:250~280 分,稳稳达到江西省一等奖分数线。
本场的核心经验
- T1 数学化简是关键:平方展开 + 前缀和优化,是 CSP-J 的常见套路
- T2 找规律决定成败:发现"拆分一次增加 2 个数"是突破口,三进制的性质需要熟悉
- T3 大胆猜想、小心求证:几何题往往有简洁结论,多用样例验证
- T4 分层 BFS 是经典模型:即使拿不到满分,\(k=0\) 的部分分也要稳稳拿到
备考建议
- 多做历年 CSP-J 真题,熟悉 T1/T2 的难度和套路
- 练好基础算法:前缀和、差分、贪心、二分、BFS/DFS
- 掌握部分分技巧:暴力、打表、特殊性质
- 考前模拟限时训练,培养时间管理意识
- T1 和 T2 绝对不能翻车——这是省一的基础保障
本课件未包含完整 AC 代码。同学们应根据讲评思路独立完成代码实现,这样才能真正提高。
浙公网安备 33010602011771号