2024年12月真题
一、单选题(每题2分,共30分)
正确答案:B
解析:
1、前 4 位中可以有最多 1 位英文字母,且英文字母不能是 O 或 I。
1)前 4 位的可能性分析:若前 4 位都是数字,每位有 10 种可能性(0 - 9),则有 10101010=10000 种可能性。
2)若前 4 位中有 1 位是英文字母(24 种可能性,除去 O 和 I),则有 4 种位置可以放字母,其余 3 位是数字,有424101010=96000种可能性。
2、第 5 位必须是数字,有 10 种可能性(0 - 9)。
总可能性为10(10000+96000)=1060000种,答案为B。
正确答案:A
解析:
1、首先将每家看作一个整体,对这四家进行圆排列,圆排列的公式为
(
n
−
1
)
!
(n-1)!
(n−1)!,这里
n
=
4
n=4
n=4,所以四家的圆排列有
(
4
−
1
)
!
=
6
(4-1)!=6
(4−1)!=6种。
2、然后考虑每家内部的排列:对于三口之家,内部排列有
3
!
3!
3! 种。对于两口之家,内部排列有
2
!
2!
2!种。
3、本题有“主座”的习俗,每个座位都被认为是不同的。主座的可能性有10种。
所以总的排列方式为
6
∗
(
3
!
)
2
∗
(
2
!
)
2
∗
10
=
8640
6*(3!)^2*(2!)^2*10=8640
6∗(3!)2∗(2!)2∗10=8640。
正确答案:D
解析:在 C++ 中,抽象类本身的定义是合法的,即使没有类继承它也不会产生编译错误,只是不能实例化。
正确答案:D
解析:对于一个有向图,使用邻接表存储。
邻接表中每个顶点对应一个链表,链表中的节点表示从该顶点出发的边所指向的顶点。
边的数量为
e
e
e 条,每个边在邻接表中对应一个节点(因为是出边表)。
正确答案:C
解析:考察C++ 语言中将二维数组作为参数的函数声明,对于二维数组,必须指定除最左边一维之外的其他维的大小。
选项 A:声明二维数组参数时,除了最左边的维数可以不指定大小外,其他维数必须指定大小,这里第二维没有指定大小。
选项 B:二维数组作为参数时,除最左边一维外,其他维必须指定大小,这里两维都未指定大小。
选项 C:这里
(
∗
a
)
[
20
]
(*a)[20]
(∗a)[20] 表示
a
a
a 是一个指向包含 20 个 int 元素的数组的指针,符合二维数组作为函数参数的正确声明方式。
选项 D:
i
n
t
∗
a
[
20
]
int * a[20]
int∗a[20] 表示
a
a
a 是一个包含 20 个 int 指针的数组,而不是二维数组。
正确答案:C
解析:对于两点
A
(
x
a
,
y
a
)
A(x_a, y_a)
A(xa,ya) 和
B
(
x
b
,
y
b
)
B(x_b, y_b)
B(xb,yb),斜率
k
=
y
b
−
y
a
x
b
−
x
a
k=\frac{y_b-y_a}{x_b-x_a}
k=xb−xayb−ya,答案选C。
正确答案:C
解析:根据二项式定理
(
a
+
b
)
n
=
∑
k
=
0
n
C
n
k
a
n
−
k
b
k
(a+b)^n=\sum_{k=0}^{n}C_{n}^{k}a^{n-k}b^{k}
(a+b)n=∑k=0nCnkan−kbk,这里
a
=
x
,
b
=
y
,
n
=
6
a=x,b=y,n=6
a=x,b=y,n=6。
对于
x
3
y
3
x^3y^3
x3y3项,
k
=
3
,
n
−
k
=
6
−
3
=
3
k=3,n-k=6-3=3
k=3,n−k=6−3=3。
其中
C
n
k
=
n
!
k
!
(
n
−
k
)
!
C_{n}^{k}=\frac{n!}{k!(n-k)!}
Cnk=k!(n−k)!n!,这里
n
=
6
,
k
=
3
n=6,k=3
n=6,k=3。
C
6
3
=
20
C_{6}^{3}=20
C63=20,答案为C。
正确答案:B
解析:考察动态规划的相关说法。
选项 A:动态规划可以通过自底向上的递推方式或者自顶向下的递归方式(通常带有记忆化)来实现。
选项 B:递归实现动态规划方法如果不使用记忆化,其时间复杂度会远高于递推实现,因为会有大量重复计算。即使使用记忆化,递归实现由于函数调用的开销,其时间复杂度通常也不会低于递推实现。
选项 C:动态规划的核心思想就是将一个复杂问题分解为多个重叠的子问题,然后求解子问题并合并结果。
选项 D:动态规划通常需要根据问题的最优子结构性质列出递推公式来求解子问题。
个人觉得本题没有错误选项。但官方答案给的B。强行解释一下:通常情况下,递归实现动态规划如果不采用记忆化,时间复杂度会高于递推实现。但如果递归实现采用了有效的记忆化技术,并且递推实现中存在一些额外的耗时操作,那么递归实现的时间复杂度可能不高于递推实现。
正确答案:D
解析:
zuhe_next 函数,需要提供三个参数:第一个参数 c 是表示当前组合所对应的整数;第二个参数 n 和第三个参数 m 表示从 n 个数中选 m 个。功能:按组合对应的整数由大到小的顺序,求出组合 c 的下一个组合。
zuhe_next_incur 函数是用于计算下一个组合的辅助函数,根据 n 和 l 的值来更新组合 c。
intlow2函数的目的是找到c中最低位的 1 所在的位置。
D选项中:只有D选项的运算结果符合intlow2函数的功能需求。
//找到c最低位的1所在的位置。
int intlow2(int c) {
return ((((c - 1) ^ c) + 1) >> 1);
}
//功能相同代码
//int intlow2(int c) {
// int position = 0;
// while ((c & 1) == 0) {
// c >>= 1;
// position++;
// }
// return 1 << position;
//}
int zuhe_next_incur(int c, int n, int l) {
//当n==1时,直接返回c,因为如果只有一个元素,组合就是它本身
if (n == 1) return c;
if ((c & (1 << l)) == 0) { //如果c的二进制表示中从右数第 l + 1 位是 0
int d = intlow2(c); //找到c最低位的1所在的位置。
c = (c & ~d); //将c中d所对应的位清 0
c = (c | (d >> 1)); //将d右移 1 位后的值与c进行按位或操作,更新c的值
} else {
c = (c & ~(1 << l)); //将c的二进制表示中第l位清 0。
c = zuhe_next_incur(c, n - 1, l + 1); //递归调用自身,减少n的值并增加l的值。相当于n-1个数中选m个
int d = intlow2(c);
c = (c | (d >> 1));
}
return c;
}
int zuhe_next(int c, int n, int m) {
return zuhe_next_incur(c, n, 0);
}
正确答案:A
解析:运行一下就有结果了,答案为A。
实际上这段代码的功能是求满足条件 x + y + z <= 15 且 x<=y<=z 的整数三元组(x, y, z)的数量。
正确答案:C
解析:考察动态规划中求两个字符串str1和str2的最长公共子序列的长度。
dp[i][j]:表示截止到字符串str1下标
i
−
1
i-1
i−1 的位置以及字符串str2下标
j
−
1
j-1
j−1 的位置能够得到的最长公共子序列的长度。dp[0][0]对应两个字符串都没开始遍历,值为0。
如果字符串str1下标
i
i
i 的字符等于字符串str2下标
j
j
j 的字符,则最长公共子序列的长度在dp[i][j]的基础上加1,则dp[i+1][j+1]=dp[i][j]+1。
否则,此时能够获得的最长公共子序列的长度应该是dp[i][j+1],和dp[i+1][j]中的最大值。
答案选C。
正确答案:B
解析:Dijkstra 算法是一种贪心算法,用于解决带权有向图(也可用于无向图,无向图可看作特殊的有向图)的单源最短路径问题。即从一个源点出发,找到到图中所有其他顶点的最短路径。
其核心思想是每次都找到当前离源点最近的未确定最短路径的顶点,将其标记为已确定最短路径,然后通过这个顶点来更新它的邻接顶点到源点的距离。
第27-29实现的功能是更新它的邻接顶点到源点的距离。如果通过新的结点路径更短,则更新,否则不更新。答案选B。
正确答案:B
解析:计算时间复杂度会忽略常数因子和低阶项。因此我们只需要看16-30代码。
外层循环:每次寻找一个离源点最近的未确定最短路径的顶点,将其标记为已确定最短路径的顶点,有多少顶点就需要找多少次,则循环次数为顶点数v
内层循环:18-23:循环次数v次,用于找未确定最短路径且离源点最近的顶点。
27-29:循环次数为:满足条件顶点的边的数量,该数量不可能超过顶点的数量。
因此内层循环的次数的数量级也为v。
综上,时间复杂度为
O
(
v
2
)
O(v^2)
O(v2),答案为B。
实际上,在学习Dijkstra 算法时,老师们一定带大家分析过该算法的时间复杂度,无脑选B就ok。
正确答案:A
解析:快速排序是一种基于分治策略的排序算法。它的基本思想是选择一个基准元素(pivot),通过一趟排序将待排记录分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序。
具体操作过程中,通常设置两个指针,一个从左向右(l),一个从右向左(r),通过比较指针所指元素与基准元素的大小关系来交换元素,最终将基准元素放到合适的位置,使得左边元素都小于等于它,右边元素都大于等于它。
第一个横线处(while循环条件):此处应该是循环进行划分操作的条件,即只要l小于r,就继续循环。所以应填入l < r。
第二个横线处(quick_sort函数的参数):第一次调用quick_sort是对基准元素左边的子数组进行排序,第二次调用是对基准元素右边的子数组进行排序。
对于左边子数组,参数应该是quick_sort(a, pivot),对于右边子数组,参数应该是quick_sort(a + pivot + 1, n - pivot - 1)。所以此处应填入a + pivot + 1, n - pivot - 1。
答案选A。
这里代码看起来跟平时所学的快速排序代码可能有所区别,要注意这里快速排序函数形参是数组和数组大小,这个数组即当前要操作的全部,传参的时候要注意传递的内容要符合需求。
正确答案:D
解析:快速排序时间复杂度,直接选D。
二、判断题(每题2分,共20分)
正确答案:错误,正确,正确
解析:
第1题错误:在 C++ 语言中,字符常量(如 ‘3’、‘5’)在参与算术运算时,实际上是使用它们对应的 ASCII 码值进行计算,运算结果是整数类型。
第2题正确:在 C++ 中,确实可以在函数内定义结构体。这种在函数内定义的结构体类型,其作用域仅限于该函数内部,外部函数无法使用这个结构体类型。。
第3题正确:表述完全正确。
正确答案:错误,错误
解析:
第4题错误:数组在内存中是连续存储的,二维数组也是,一行一行进行存储,第一行存完紧跟着存储第二行。例如定义二维数组 int a[4][5],存储元素a[0][4]之后紧跟着存储元素a[1][0]。
第5题错误:没有指定时,log函数通常是以自然对数(底数为e)进行计算的,运算结果类型为double。log(1000)的值约等于6.907755。如果要得到3,可指定底数,即log10(1000)的结果约等于3。
正确答案:正确
解析:动态规划-凑零钱问题。
设dp[i]表示凑出金额i的组合数。
dp[0] = 1,因为凑出 0 元有一种方法,就是什么都不选。
动态转移方程:
对于每个金额i(i > 0),如果i大于等于硬币的面值coin,则dp[i] += dp[i - coin]。
可得凑27元的组合数为8。
正确答案:错误,错误
解析:
第7题错误:哈希函数
f
(
x
)
=
x
%
p
f(x)=x\%p
f(x)=x%p 是一种常见的取余哈希函数。即使 p 取小于等于哈希表大小的素数,也不能保证不发生碰撞。
例如,当哈希表大小为7,p=5(5是素数且小于等于7)时:对于键值5和10,哈希函数的计算结果都为0,会发生碰撞。
哈希函数只能尽量减少碰撞的概率,但不能完全消除碰撞,说法错误。
第8题错误:杨辉三角是一个由数字排列成的三角形数阵,其具有以下性质:
1)第n行(n从 0 开始计数)有n+1个数。
2)每行两端的数都是 1。
3)每个数等于它上方两数之和。
二项式
(
a
+
b
)
n
(a+b)^n
(a+b)n展开式的系数与杨辉三角的第行的数一一对应。例如:
(
a
+
b
)
0
=
1
(a+b)^0=1
(a+b)0=1,对应杨辉三角第 0 行:1。
(
a
+
b
)
1
=
a
+
b
(a+b)^1=a+b
(a+b)1=a+b,对应杨辉三角第 1 行:1 1。
(
a
+
b
)
2
=
a
2
+
2
a
b
+
b
2
(a+b)^2=a^2+2ab+b^2
(a+b)2=a2+2ab+b2,对应杨辉三角第 2 行:1 2 1。
(
a
+
b
)
3
=
a
3
+
3
a
2
b
+
3
a
b
2
+
b
3
(a+b)^3=a^3+3a^2b+3ab^2+b^3
(a+b)3=a3+3a2b+3ab2+b3,对应杨辉三角第 3 行:1 3 3 1。
这个官方答案为错误,可以解释为:杨辉三角中的第n行、第m项没有限定n、m是从0开始的。
正确答案:正确,错误
解析:
第9题正确: 判断图是否连通,既通过广度优先搜索实现,也可以通过深度优先搜索实现。调用一次搜索函数,如果本次搜索可以访问到所有结点,则图是连通的,否则不连通。
第10题错误:一元二次方程
a
x
2
+
b
x
+
c
=
0
ax^2+bx+c=0
ax2+bx+c=0求解的公式为:
−
b
±
b
2
−
4
a
c
2
a
\frac{-b\pm\sqrt{b^2-4ac}}{2a}
2a−b±b2−4ac。如果根存在,此式要有意义,也就是
b
2
−
4
a
c
>
=
0
b^2-4ac>=0
b2−4ac>=0 且
a
!
=
0
a!=0
a!=0。在本题中:a值为1, b值为a,c值为b,代入得:
a
2
−
4
b
>
=
0
a^2-4b>=0
a2−4b>=0。考虑到C++中运算符的形式,表达式应该写成
a
∗
a
−
4
∗
b
>
=
0
a*a-4*b>=0
a∗a−4∗b>=0,C++中,符号^表示位运算中的异或运算。因此本题表述错误。
三、编程题(每题25分,共50分)
在至多经过 k 个黑色节点的前提下,经过的总节点数尽可能多。可以任意选择节点 s 和节点 t 并从节点 s 出发移动到节点 t,不能够经过重复节点。
从任意一个节点出发深搜,达成条件(经过的黑色节点数超过了k个)停止搜索,在搜索的过程中记录经过的总结点数,并且不断和结果值res进行比较,res记录经过的总结点数的最大值。
#include <bits/stdc++.h>
using namespace std;
const int N=1001;
int arr[N];
vector<int> mp[N];
int n, k, u, v, res;
//cur:当前节点,pre:上一个节点
//black:黑色节点的个数,sum:总节点的个数
void dfs(int cur, int pre, int black, int sum) {
black+=arr[cur]; //更新黑色节点的个数
if(black > k) { //如果黑色节点已经超出k个,计算更新,结束搜索
res = max(res, sum);
return;
}
sum++; //更新总节点的个数
res = max(res, sum);
for(int i=0; i<mp[cur].size(); i++) {
int next=mp[cur][i]; //下一个要访问的节点
if(next != pre){ //将访问的节点不是上一个节点,防止重复搜索
dfs(next, cur, black, sum);
}
}
}
int main() {
cin>>n>>k;
for(int i=1; i<=n; i++) cin>>arr[i];
for(int i=1; i<n; i++) {
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
}
for(int i=1; i<=n; i++) {
dfs(i, 0, 0, 0);
}
cout<<res;
return 0;
}
n位同学排成一行队伍,其中存在这样的约束,给出关系< a i , b i a_i, b_i ai,bi> ,表示排队时编号为 i i i 的同学需要排在编号为 j j j 的同学前面,并且两人在队伍中相邻,这样的关系一共给m对,m>=0。
这是一个带约束的排列问题。很显然,如果所有同学之间都没有关系,则排队方式为全排列:n!。 现在同学之间有m对关系,前后关系有要求且必须相邻,可将有关系的同学先进行聚集,然后看聚集后有多少堆,则堆数的全排列即是排队方式的数量。
更细节的的要求和解决方案
- 一个同学前边最多有一个同学,后边也最多有一个同学,给定的关系如果不符合这个要求则无法进行排队。
- 排队不能有环,如果给定的关系导致环出现也无法进行排队。
- 同学聚集可使用搜索的思路解决,顺着pre往前,以及顺着nxt往后进行搜索,搜索的同时可以检测是否有环。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+1;
const int mod=1e9+7;
int pre[N], nxt[N];
bool vis[N];
int main() {
int n, m, a, b;
cin>>n>>m;
for(int i=1; i<=m; i++){
cin>>a>>b;
//处理重复输入
if(pre[b]==a && nxt[a]==b) continue;
/*不是重复输入,但pre[b]和next[a]已赋值,说明一个人有多个后相邻,
或者一个人有多个前相邻,这是不符合题目描述的。结果直接为0
*/
if(pre[b] || nxt[a]){
cout<<0;
return 0;
}
pre[b]=a; nxt[a]=b;
}
/*如果所有同学之间都没有关系,则排队方式为全排列:n!
现在同学之间有m对关系,前后有要求且必须相邻,将有关系的同学先进行聚集,
然后将聚集的一堆一堆的同学进行全排列,那现在的问题就是有几堆同学,这个可以用
搜索的思路解决,很显然同学排队是个单链关系,顺着pre往前,以及nxt往后进行搜索,
将能够搜索到的算成一堆。要注意的是,搜索如果出现环,也是不符合题目描述的,
可以直接输出0。
*/
int cnt=0;
for(int i=1; i<=n; i++){
if(!vis[i]){ //一个没有被访问过的同学,意味着一个新堆
vis[i]=true;
int j=nxt[i]; //顺着nxt往后搜索
while(j){
if(vis[j]){ //遇到访问过的,意味着有环
cout<<0;
return 0;
}
vis[j]=true;
j=nxt[j];
}
j=pre[i]; //顺着pre往前搜索
while(j){
if(vis[j]){ //遇到访问过的,意味着有环
cout<<0;
return 0;
}
vis[j]=true;
j=pre[j];
}
cnt++; //无环,增加一个有效的堆
}
}
//有效的堆全排列
long long res=1;
for(int i=1; i<=cnt; i++){
res = res*i%mod;
}
cout<<res;
return 0;
}