在 C++ 中,下面哪个关键字用于声明一个值不能被修改的变量?
🧭 逐项辨析
const = 常量,声明后值不可修改。unsigned 是无符号修饰;static 是静态存储期;mutable 恰相反——允许在 const 对象里修改该成员。
const int x=5; 之后 x 不可被赋值。mutable 用于类成员,突破 const 限制。试卷:CSP 2023 入门级(Junior)第一轮 · 单项选择 + 阅读程序 + 完善程序
用法:先独立作答,再点「显示解析」核对分步推理;右上角可切换 浅色/深色 配色。
CSP-J 入门级第一轮。本卷可归为 8 大板块,阅读/完善程序是拉分重点。
Q1 const、Q3 union 联合体、Q4 链表头插。
Q2 八进制加法、Q9 二/八/十六进制互转。
Q5 三叉树高度、Q8 后缀转中缀、Q10 哈夫曼、Q11 遍历还原。
Q12 拓扑排序。
Q6 不相邻选段、Q14 至少一女补集法。
Q7 高精度、Q13 存储单位、Q15 操作系统。
海伦公式求面积、LCS+字符串旋转、因子平方和。
二分查找被移除元素、编辑距离 DP。
在 C++ 中,下面哪个关键字用于声明一个值不能被修改的变量?
const = 常量,声明后值不可修改。unsigned 是无符号修饰;static 是静态存储期;mutable 恰相反——允许在 const 对象里修改该成员。
const int x=5; 之后 x 不可被赋值。mutable 用于类成员,突破 const 限制。八进制数 123456708 和 076543218 的和为?
从右往左对齐相加(八进制,满 8 进位):
| 位(高→低) | 1 2 3 4 5 6 7 0 |
|---|---|
| + | 0 7 6 5 4 3 2 1 |
最低位 0+1=1;次低 7+2=9=8+1 → 写 1 进 1;6+3+1=10=8+2 → 写 2 进 1;5+4+1=10 → 写 2 进 1;…一路下来得 222222118。(注意中间会连续产生进位。)
给 union Data data; 的 value 成员存 3.14,正确写法是?
data 是联合体对象(不是指针),访问成员用点号:data.value。-> 用于指针;B、D 把对象名和成员名写反了。
obj.member;指针 ptr->member(等价 (*ptr).member)。union 同一时刻只能有效保存一个成员。在链表头部插入 data=42 的新节点,正确操作是?
① 给新节点赋值 newNode->data=42;② 新节点指向原头 newNode->next=head;③ 更新头指针 head=newNode。三步缺一不可。
B 错在改了 head 的 data;C 把新节点接到了头的后面(不是头);D 漏了更新 head(新节点没真正成为头)。
根节点高度为 1,一棵有 2023 个节点的三叉树高度至少为?
三叉树第 k 层最多 3k−1 个节点,h 层全满共 (3h−1)/2 个。要装下 2023,找最小 h 使 (3h−1)/2 ≥ 2023:
| h | 5 | 6 | 7 | 8 |
|---|---|---|---|---|
| 最多节点 (3h−1)/2 | 121 | 364 | 1093 | 3280 |
h=7 最多 1093 < 2023,h=8 最多 3280 ≥ 2023。所以高度至少 8。
7 个时间段,选至少 1 个练歌,任意两个被选段之间至少隔 2 个空闲段。共多少种方案?
「中间隔 ≥2 个」意味着相邻所选编号之差 ≥3。选 k 个的方案数 = C(7−2(k−1), k):
| 选 k 个 | 算式 C(7−2(k−1), k) | 种数 |
|---|---|---|
| 1 | C(7,1) | 7 |
| 2 | C(5,2) | 10 |
| 3 | C(3,3) | 1 |
| ≥4 | C(1,4)=0 | 0 |
合计 7+10+1 = 18。
以下关于高精度运算的说法错误的是?
朴素高精度乘法是「每一位乘每一位」,时间复杂度 O(m×n),与两个数的位数都有关,不是只看较长者。所以 C 错误。A、B、D 都正确。
后缀表达式 6 2 3 + - 3 8 2 / + * 2 ^ 3 + 对应的中缀表达式是?
| 读到 | 栈顶表达式 |
|---|---|
| 6 2 3 + | 6, (2+3) |
| − | (6−(2+3)) |
| 3 8 2 / | (6−(2+3)), 3, (8/2) |
| + | (6−(2+3)), (3+8/2) |
| * | ((6−(2+3))*(3+8/2)) |
| 2 ^ | ((6−(2+3))*(3+8/2))^2 |
| 3 + | ((6−(2+3))*(3+8/2))^2+3 |
注意 ^2 作用在整个乘积上(C 误把幂只套在 3+8/2 上)。
(左 运算符 右) 再入栈。运算符作用范围 = 它弹出的那两个完整子表达式。1010102 和 1668 的和为?
1010102 = 32+8+2 = 42;1668 = 64+48+6 = 118。和 = 42+118 = 160。
逐个验算选项:A=176、B=158、C=158、D=(A0)16=10×16+0=160。只有 D 等于 160。
字符 a~f 频率为 5%,9%,12%,13%,16%,45%。哪组是对应的哈夫曼编码?
反复合并两个最小频率:5+9=14 → 12+13=25 → 14+16=30 → 25+30=55 → 55+45=100。各字符码长:
| 字符 | a | b | c | d | e | f |
|---|---|---|---|---|---|---|
| 频率% | 5 | 9 | 12 | 13 | 16 | 45 |
| 码长 | 4 | 4 | 3 | 3 | 3 | 1 |
选项 A 的码长正好是 4,4,3,3,3,1,且前缀不冲突(f=0,其余以 1 开头),符合哈夫曼。其余选项码长或前缀不对。
前序 ABDECFG、中序 DEBACFG,求后序遍历。
根 = A(前序首位)。中序 DEB | A | CFG → 左子树 {D,E,B},右子树 {C,F,G}。
左子树:前序 BDE、中序 DEB → 根 B,B 的左子树是「D 带右孩子 E」。
右子树:前序 CFG、中序 CFG → 根 C,C 的右子树是「F 带右孩子 G」。
后序 = 左→右→根:左子树后序 E,D,B;右子树后序 G,F,C;最后 A → EDBGFCA。
DAG 有边 (1,2),(1,3),(2,4),(3,4)。哪个是有效的拓扑排序?
约束:1 在 2、3 前;2 在 4 前;3 在 4 前。
| 选项 | 违反 |
|---|---|
| B 1,2,3,4 | 全部满足 ✓ |
| A 4,2,3,1 | 4 在最前,违反 2→4 等 |
| C 1,2,4,3 | 4 在 3 前,违反 3→4 |
| D 2,1,3,4 | 2 在 1 前,违反 1→2 |
以下哪个描述的数据存储容量最小?
bit(位,0/1)< byte(字节,8 位)< word(字,通常 2 或 4 字节)< KB(1024 字节)。最小的是 比特 bit。
10 男 12 女选 3 人,至少含 1 个女生,有多少种组合?
从 22 人选 3:C(22,3) = 1540。其中「一个女生都没有(全男)」= C(10,3) = 120。
至少 1 女 = 1540 − 120 = 1420。
以下哪个不是操作系统?
Linux、Windows、Android 都是操作系统;HTML(超文本标记语言)是用于编写网页的标记语言,不是操作系统。
📄 阅读程序(一)· 海伦公式求三角形面积
输入三边 a,b,c(正整数),用海伦公式 S=√(s(s−a)(s−b)(s−c)) 算面积,s 为半周长。fixed+precision(4) 固定 4 位小数。
double f(double a,double b,double c){
double s=(a+b+c)/2;
return sqrt(s*(s-a)*(s-b)*(s-c)); // 海伦公式
}
int main(){
cout.flags(ios::fixed); cout.precision(4); // 固定 4 位小数
int a,b,c; cin>>a>>b>>c;
cout<<f(a,b,c)<<endl;
}
当输入为 2 2 2 时,输出为 1.7321。
s=(2+2+2)/2=3,S=√(3·1·1·1)=√3≈1.7320508,保留 4 位四舍五入 → 1.7321。
将 (s-b)*(s-c) 改为 (s-c)*(s-b) 不会影响结果。
(s-b)*(s-c) 与 (s-c)*(s-b) 数值完全相等(乘法交换律),结果不变。
程序总是输出四位小数。
cout.flags(ios::fixed) 配合 precision(4) 表示小数点后固定 4 位,无论数值大小都输出 4 位小数(如 6.0000)。
fixed 下 precision(k) 表示小数位数;不加 fixed 时 precision 表示有效数字位数。当输入为 3 4 5 时,输出为?
3,4,5 满足 3²+4²=5²,是直角三角形,面积 = 两直角边乘积 /2 = 3×4/2 = 6。输出 6.0000。
(验算海伦:s=6,S=√(6·3·2·1)=√36=6 ✓。)
当输入为 5 12 13 时,输出为?
5²+12²=25+144=169=13²,直角三角形,面积 = 5×12/2 = 30。输出 30.0000。
📄 阅读程序(二)· 最长公共子序列 + 字符串旋转判定
f(x,y) 是经典 LCS(最长公共子序列)DP。g(x,y):长度不同直接 false;否则判断 LCS(x+x, y)==|y|,即 y 是否为 x 的循环旋转。cout<<bool 输出 0/1。
int f(string x,string y){ // LCS 长度
int m=x.size(), n=y.size();
vector<vector<int>> v(m+1, vector<int>(n+1,0));
for(int i=1;i<=m;i++) for(int j=1;j<=n;j++)
if(x[i-1]==y[j-1]) v[i][j]=v[i-1][j-1]+1;
else v[i][j]=max(v[i-1][j], v[i][j-1]);
return v[m][n];
}
bool g(string x,string y){
if(x.size()!=y.size()) return false;
return f(x+x, y)==y.size(); // y 是 x 的旋转?
}
// main: 读 x y; 输出 g(x,y) (0/1)
f 函数的返回值小于等于 min{n, m}。
LCS 是两串的公共子序列,它的长度不会超过任一原串长度,所以 ≤ min(m, n)。
f 函数的返回值等于两个输入字符串的最长公共子串的长度。
这段 DP(不匹配时取 max(上, 左))求的是最长公共子序列(可以不连续)。而子串必须连续,是另一种 DP(不匹配时清 0)。两者一般不相等,所以题述说成「子串」是错的。
例:x="abc", y="aXbXc",LCS(子序列)=3(a,b,c),最长公共子串只有 1。
max(v[i-1][j],v[i][j-1]));子串必须连续(不匹配时归 0)。一字之差,算法不同。当输入两个完全相同的字符串时,g 的返回值总是 true。
x=y 时长度相等。x 是 x+x 的前半段,所以 LCS(x+x, x)=|x|,于是 f(x+x,x)==|x| 成立,g 返回 true。
将第 19 行 v[m][n] 替换为 v[n][m],那么该程序?
f 只被 g 调用,参数是 f(x+x, y),所以 m=|x+x|=2L、n=|y|=L,m≠n。v 的大小是 (m+1)×(n+1)=(2L+1)×(L+1)。
访问 v[n][m]=v[L][2L]:行 L 合法,但列 2L 超过了每行 L+1 的范围 → 数组越界。越界是未定义行为,可能崩溃、也可能侥幸读到垃圾值,故「可能非正常退出」。
vector::operator[] 不做边界检查,越界是未定义行为(UB),结果不确定——可能崩溃可能不崩。所以是「可能」而非「一定」。当输入为 csp-j p-jcs 时,输出为?
x="csp-j" 的所有循环旋转:csp-j → sp-jc → p-jcs → -jcsp → jcsp-。y="p-jcs" 正是其中之一,所以 g 返回 true。
cout<<bool 把 true 输出为 1(不是 T/F)。
cout 默认把 bool 输出成 0/1(除非加 boolalpha 才显示 true/false)。所以答案是数字 1。当输入为 csppsc spsccp 时,输出为?
注意 g 用的是 LCS(子序列)而非严格旋转。运行程序:f("csppsccsppsc", "spsccp") 恰好等于 6=|y|,所以 g 返回 true,输出 1。
(直觉上 "spsccp" 能作为 "csppsc csppsc" 的一个长度 6 的公共子序列被匹配出来。)
📄 阅读程序(三)· 因子平方和
solve1(n)=n²。solve2(n) 枚举 i 到 √n,对每对因子 (i, n/i) 累加平方,i=√n 时只算一次。即 solve2(n) = n 的所有因子的平方和。
int solve1(int n){ return n*n; }
int solve2(int n){
int sum=0;
for(int i=1;i<=sqrt(n);i++)
if(n%i==0){
if(n/i==i) sum+=i*i; // 平方根因子只算一次
else sum+=i*i+(n/i)*(n/i); // 一对因子 i 和 n/i
}
return sum;
}
// main: 输出 solve2(solve1(n)) 和 solve1(solve2(n))
若输入 n 为正整数,solve2 的作用是计算 n 所有因子的平方和。
枚举 i 到 √n,每找到一个因子 i 就同时得到配对因子 n/i,把 i²+(n/i)² 累加;当 i=n/i(完全平方根)只加一次。这样不重不漏地累加了所有因子的平方。
第 13~14 行的作用是避免平方根因子 i(或 n/i)被计算两次。
当 n 是完全平方数时,i=√n 满足 i=n/i。若不特判,会把这个因子的平方 i*i+(n/i)*(n/i)=2i² 加两次。第 13~14 行 if(n/i==i) sum+=i*i 保证它只加一次。
若输入 n 为质数,solve2(n) 的返回值为 n²+1。
质数 n 的因子是 1 和 n,平方和 = 1² + n² = n²+1。
若输入 n 为质数 p 的平方,则 solve2(n) 的返回值为?
n=p² 的全部因子:1、p、p²。平方和 = 1 + p² + p⁴。
而 n=p²,所以 n²+n+1 = (p²)² + p² + 1 = p⁴ + p² + 1,正好相等!故答案 n²+n+1。
注意 D(p⁴+2p²+1=(p²+1)²)多了一个 p²,是错的。
当输入为正整数时,第一项减去第二项的差值一定?(第一项 solve2(n²),第二项 solve2(n)²)
第一项 = solve2(n²) = n² 的因子平方和;第二项 = (solve2(n))² = n 的因子平方和再平方。
展开 (solve2(n))² = (Σ dᵢ²)² 会产生很多交叉项 2dᵢ²dⱼ²,而 solve2(n²) 只是 n² 各因子的平方和。代入小例子:
| n | solve2(n²) | (solve2(n))² | 差 |
|---|---|---|---|
| 1 | 1 | 1 | 0 |
| 2 | 21 | 25 | −4 |
| 3 | 91 | 100 | −9 |
差始终 ≤0,且只有 n=1 时等于 0、其余都 <0。所以「小于等于 0 且不一定小于 0」。
当输入为 5 时,输出为?
第一项 = solve2(solve1(5)) = solve2(25)。25 的因子 1,5,25,平方和 = 1+25+625 = 651。
第二项 = solve1(solve2(5)) = (solve2(5))²。solve2(5)=1²+5²=26,26² = 676。
输出 651 676。
🧩 完善程序(一)· 二分查找被移除的元素
原是公差为 1 的等差数列(长 n+1),移除一个元素后剩 n 个。若某下标处 nums[mid] ≠ nums[0]+mid,说明缺口在左半;用二分定位被移除的值。补全 ①~⑤。
int find_missing(vector<int>& nums){
int left=0, right=nums.size()-1;
while(left<right){
int mid = left+(right-left)/2;
if(nums[mid] == mid + ①) ②; // 左半完整 → 往右找
else ③; // 缺口在左半(含 mid)
}
return ④; // 被移除的值
}
// main: if (missing == ⑤) 输出 "consecutive"; else 输出缺失值
① 处应填?
若 [0..mid] 没被删过,则 nums[mid] 应等于首项 nums[0] 加上偏移 mid,即 nums[mid]==nums[0]+mid。所以 ① = nums[0]。
首项 + i。和实际值比较即可判断缺口在哪侧。② 处应填?
当 nums[mid]==nums[0]+mid,说明 0~mid 都没缺,缺口在 mid 右侧,下界右移:left=mid+1。
③ 处应填?
当 nums[mid]≠nums[0]+mid,说明缺口在 0~mid 之间(mid 本身可能就是错位点),所以 right=mid(保留 mid,不能写 mid−1)。
配合 ② 的 left=mid+1,这是「找第一个错位位置」的标准二分。
right=mid(保留),不满足时 left=mid+1,最终 left==right 即答案。④ 处(返回被移除的值)应填?
循环结束时 left(==right)就是第一个错位下标,被移除的值应该填在这个位置,其值 = nums[0]+left。
注意 mid 是循环内局部变量,函数返回处不能用(B 的 right 与 left 相等也对,但标准答案用 left)。
⑤ 处(判定「数列连续无缺失」的哨兵)应填?
当数组本身连续(被删的是第一个或最后一个),二分不会找到错位,left 一路推到 n−1,返回 nums[0]+(n-1),而这正好等于末元素 nums[n-1]。所以用 missing==nums[n-1] 判断「连续」。
为什么不能用 B(nums[0]+n−1)?若缺口恰在倒数第二个位置,函数也返回 nums[0]+n−1,但此时 nums[n−1]=nums[0]+n(更大),用 D 能区分、用 B 会误判为连续。已用对拍确认只有 D 全对。
🧩 完善程序(二)· 编辑距离(Levenshtein)
求把 str1 变成 str2 的最少操作(插入/删除/替换)次数。dp[i][j] = str1 前 i 个变成 str2 前 j 个的最少操作数。补全 ①~⑤。
int edit_dist_dp(string str1, string str2){
int m=str1.length(), n=str2.length();
vector<vector<int>> dp(m+1, vector<int>(n+1));
for(int i=0;i<=m;i++) for(int j=0;j<=n;j++){
if(i==0) dp[i][j] = ①; // 空串 → str2 前 j 个:插 j 次
else if(j==0) dp[i][j] = ②; // str1 前 i 个 → 空串:删 i 次
else if(③) dp[i][j] = ④; // 末字符相同:免操作
else dp[i][j] = 1 + min(dp[i][j-1], dp[i-1][j], ⑤); // 插/删/换
}
return dp[m][n];
}
① 处应填?
i==0:str1 是空串,要变成 str2 的前 j 个字符,需插入 j 次,所以 dp[0][j]=j。
② 处应填?
j==0:str2 是空串,要把 str1 的前 i 个字符全删掉,需删除 i 次,所以 dp[i][0]=i。
③ 处(判断末字符是否相同)应填?
dp[i][j] 对应 str1 的前 i 个、str2 的前 j 个,它们的最后一个字符分别是 str1[i-1] 和 str2[j-1](字符串下标从 0 开始)。相等就走「免操作」分支,所以条件是 str1[i-1]==str2[j-1]。
④ 处(末字符相同时)应填?
当 str1[i-1]==str2[j-1],最后一个字符天然对齐、无需任何操作,所以 dp[i][j] 直接继承 dp[i-1][j-1](不加 1)。
⑤ 处(不同时,三种操作之一)应填?
末字符不同时,取三种操作的最小代价 + 1:
| 操作 | 来源 |
|---|---|
| 插入 | dp[i][j−1] |
| 删除 | dp[i−1][j] |
| 替换 | dp[i−1][j−1] |
已给出前两个,⑤ 是替换对应的 dp[i-1][j-1]。整体 1+min(插,删,换)。