2023年12月真题
一、单选题(每题2分,共30分)
正确答案:B
考察知识点:数学函数
解析:log10(x)表示 10 的多少次方是 x,log2(x)表示2 的多少次方是x,这里的 x 是输入的 100,所以,log10(100)=2,又因为 26=64,所以log2(100)是6. 多,两者作差,约为-4.多,选 B。
正确答案:D
考察知识点:动态规划-石子合并
解析:首先看代码,s 数组是前缀和数组,f 数组是dp 数组,初始化f 数组为正无穷,只有 f[i][i]=0(1<=i<=n)的值为 0,接着进行了区间dp,i 和j 分别是区间 dp 的两个端点,k 是枚举的分界点,k 的取值范围是[i,j),所以选项C 错误,根据第 15 行转移方程,发现后面的 s[j]-s[i-1]是 a[i]+a[i+1]+…+a[j]的和,且与k无关,可以单独拎出来,所以转移方程为 f [ i ] [ j ] = m i n ( f [ i ] [ k ] + f [ k + 1 ] [ j ] ) + ∑ k = i j a ( k ) f[i][j]=min(f[i][k]+f[k+1][j])+ {\textstyle \sum_{k=i}^{j}a(k)} f[i][j]=min(f[i][k]+f[k+1][j])+∑k=ija(k) ,选项 D 正确。选项 A,B 的错误点在于 f(i,j)的初始值为正无穷,所以f(i,j)是不参与转移方程的。
正确答案:B
考察知识点:动态规划-最长上升⼦序列
解析:题目已经提示我们这是在求最长上升子序列,f 数组的含义是以i 结尾的最长上升子序列长度,ans 是整个序列的最长上升子序列长度,代码中先依次输出了 f[1],f[2],…,f[n],最后再输出 ans,接着我们可以进行手算,1 7 3 5 9 序列的f 值分别为 1,2,2,3,4,ans=4,所以正确答案为 B。
正确答案:C
考察知识点:C++语法
解析:static 是静态意思,可以修饰成员变量和成员方法,static 修饰成员变量表示该成员变量在内存中只存储一份,可以被共享访问,修改。选项C 中a 的地址都可以访问是不对的,所以本题选 C。
正确答案:D
考察知识点:图
解析:注意到题目里说的是非连通无向图,那么在同样的点数n 下,为了有尽量多的边,可以分为两张连通图,一张 n-1 个点的完全图,另一张只有单独一个点,手算后可以发现,8 个点的完全图有 8*7/2=28 个点,正好满足题目要求,所以总点数为 9 个,选 D。
正确答案:D
考察知识点:哈希表
解析:题目提示我们这是哈希表,根据代码,发现是按照%13 进行哈希并且在发生冲突的情况下, 对应放到下一个位置,我们依次计算17 28 30 4 会放置在什么位置,17 放置在 4,28 放置在 2,30 本来放置在 4,但是发生冲突,最终放置在 5,4 本来放置在 4,但是 4 和 5 都被占用了,所以最终放置在6,选D。
正确答案:B
考察知识点:二叉树
解析:先序遍历是根左右,中序遍历是左根右,首先可以根据先序遍历和中序遍历画出完整的树,如下图:
所以正确答案为 B。
正确答案:C
考察知识点:动态规划-最长公共⼦串
解析:题目告诉我们代码是在求解最长公共子串,代码中使用了双重for 循环,且循环范围为[1n1]以及[1n2],所以选项 A 正确,使用了二维数组dp,两维的长度也均为字符串长度,所以选项 B 正确,空间复杂度还可以使用滚动数组进一步优化为 O(n),所以选项 C 错误,本题选 C,选项 D 正确,代码中使用的正是动态规划算法。
正确答案:B
考察知识点:图
解析:图的广度优先搜索是从若干点出发,依次向外进行逐层扩展的算法,使用队列存放待遍历节点,本题选 B。
正确答案:C
考察知识点:哈希表
解析:代码采用链地址法来存储哈希,即将所有哈希地址相同的记录都链接在同一链表中,哈希方式为%7,我们依次对每个元素进行判断:44,36,23,35,52,73,90, 58,每个数字的哈希地址分别是 2,1,2,0,3,3,6,2,即哈希值为0~6的元素个数分别有 1,1,3,2,0,0,1,对于之前的 8 个数字,它们查找成功的次数分别是 1,1,2,1,1,2,1,3,总次数为 12 次,平均次数=12/8=1.5 次,答案为C。
正确答案:D
考察知识点:图
解析:查询有向图上有多少个点能够到达该点,可以在反图上进行搜索,所以选项 A,B,C 都可以,正确答案是 D。
正确答案:C
考察知识点:二叉树
解析:设一棵完全二叉树有 k 层,则前 k-1 都是满二叉树,第k 层的节点需要从左往右排列,那么第 1 层有 1 个节点,第 2 层有 2 个节点,第3 层有4个节点。。。第 9 层有 512 个节点,此时总节点个数为 1023,第10 层放置剩余的1000 个节点,那么叶节点个数为第 10 层的 1000 个节点,再加上第9 层除去被第 10 层消耗的 500 个节点外剩余的 12 个节点,总共为1012 个,选C。也可以根据完全二叉树的节点编号性质来计算,即:第 2023 号结点的双亲是最后一个非叶结点,序号是 2023/2=1011,所以叶节点个数为:2023-1011=1012。
正确答案:B
考察知识点:图
解析:】代码中使用了邻接表来存储边的信息,查找某个点的度时需要计算出度和入度。出度直接从该点出发,遍历该点出发的边即可。同时查询入度,可以在反图上进行类似操作,总复杂度为 O(e),选 B。也可以遍历整个邻接表,包含点顶点 u 的弧的数目就是该顶点的度。
正确答案:D
考察知识点:图
解析:对于不同的图,BFS 和 DFS 的效率也不一样,有可能DFS更快,也有可能 BFS 更快,所以本题正确答案为 D。
正确答案:B
考察知识点:图
解析:广度优先遍历会首先搜索和 s 距离为 k 的所有顶点,然后再去搜索和s距离为 k+1 的其他顶点,所以第 1 个序列不是广度优先搜索的序列,因为v3和v1 的距离超过了 v4 和 v1 的距离,但是序列中 v3 确排在v4 前面,其余3个序列都是广度优先搜索的序列,本题选 B。
二、判断题(每题2分,共20分)
正确答案:正确
考察知识点:数学函数
解析:代码将输入的角度转换成弧度,虽然对于任意的弧度,数学上均有,但 int()转换对某些 x 可能出现截断的情况,能够导致循环结束。
正确答案:正确
考察知识点:算法
解析:泛洪算法是从某个点出发,向周边相邻的区域进行扩展,和操作的要求是一致的,正确。
正确答案:错误
考察知识点:二叉树
解析:树的深度应该是 [ l o g 2 N + 1 ] [log_2N+1] [log2N+1],直接写 log 会以 e 为底,错误
正确答案:正确
考察知识点:动态规划-最大子段和
解析:问题为最大子段和,动态规划的经典例题,设f[i]为以i 结尾的子段最大值,则 f[i]=max(a[i],f[i-1]+a[i]);正确。
正确答案:正确
考察知识点:数学函数
解析:式子的左侧 exp(x),所以 log(exp(x))就等于 x,等价于询问对于任意大于 0 的正实数 x,是否有 x>log10(x),当 0<x<1 时,log10(x)为负数,必然成立,当x==1 时,log10(x)=0,成立,当 x>1 时,log10(x)的增长远远慢于x 的增长,也成立,所以 x>log10(x)成立,正确。
正确答案:错误
考察知识点:图
解析:邻接矩阵求节点 u 的度时间复杂度为 O(n),而邻接表为O(e)。
正确答案:错误
考察知识点:哈希表
解析:设 p 为 7,则键值 14 和 21 的 hash 值相同,产生了冲突。
正确答案:错误
考察知识点:动态规划
解析:用递归法求解动态规划方程会造成重复计算,可能导致超时。另外,动态规划算法的核心是状态转移方程,但同时也需要定义状态、初始条件和边界条件等。
正确答案:正确
考察知识点:图
解析:BFS 是图论中遍历图的算法,可以从任意一个点出发进行BFS,记录遍历过程中经过的不同点的个数,若不等于总点数,则说明图不连通。
正确答案:错误
考察知识点:C++语法
解析:创建对象时最多只会执行一个构造函数。
三、编程题(每题25分,共50分)
本题考察 图、搜索
给定N件商品,M种交换规则,问商品a交换到商品b的最小花费。商品是图的节点,交换规则是节点之间的边,商品a到商品b是路径,很显然本题是图中的最短路径问题。考虑到最小花费也可能是负数,因此单源最短路径算法Dijkstra算法不适用于本题,而由于本题中节点数量众多,多源最短路径算法Floyd算法会导致超时。
假定经过k次交换可以从商品a到商品b,交换过程为: a → x 1 → x 2 → . . . → x k − 1 → b a\to x_1\to x_2\to...\to x_{k-1}\to b a→x1→x2→...→xk−1→b,则交换的花费为: ( g o o d s [ x 1 ] − g o o d s [ a ] + 1 ) + ( g o o d s [ x 2 ] − g o o d s [ x 1 ] + 1 ) + . . . + ( g o o d s [ b ] − g o o d s [ x k − 1 ] + 1 ) (goods[x_1]-goods[a]+1)+(goods[x_2]-goods[x_1]+1)+...+(goods[b]-goods[x_{k-1}]+1) (goods[x1]−goods[a]+1)+(goods[x2]−goods[x1]+1)+...+(goods[b]−goods[xk−1]+1),拆括号合并最终结果为:goods[b]-goods[a]+k,goods[b]-goods[a]是个固定值,因此最少的花费也就是最少的交换次数,也就是从商品a到商品b最少经过几次交换,广度优先搜索解决的基本问题。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1, INF=2147483647;
int n, m, a, b, x, y;
int goods[N]; //商品价值
vector<int> arr[N]; //arr[i] 第i件商品可以交换的商品数组
int bfs() {
bool vis[N];
queue<int> q;
vis[a]=true;
q.push(a);
int cnt=0; //搜索的层数
while(!q.empty()) {
int n=q.size();
for(int i=0; i<n; i++) {
int f=q.front();
if(f == b) return cnt; //找到商品b,结束搜索
for(int j:arr[f]) {
if(!vis[j]) {
vis[j]=true;
q.push(j);
}
}
q.pop();
}
cnt++;
}
return INF; //返回INF,标志找不上商品b
}
int main() {
cin>>n>>m>>a>>b;
for(int i=0; i<n; i++) cin>>goods[i];
for(int i=0; i<m; i++) {
cin>>x>>y;
arr[x].push_back(y);
}
int k = bfs();
if(k==INF) cout<<"No solution";
else cout<<goods[b]-goods[a]+k;
return 0;
}
本题考察 动态规划
n轮游戏,小杨出牌计划c已知,另一个参与者进行出牌,第 i 轮游戏平得 a i a_i ai,赢得2倍,输不得分,除第一轮游戏外,允许换牌,第j次换牌扣$b_j分,问n轮游戏做多能获得多少分
考虑使用动态规划算法:dp[i][j][k]表示游戏进行了i轮,换了j次牌,当前牌为k的最大得分。
则有:
d p [ i ] [ j ] [ k ] = m a x { d p [ i − 1 ] [ j ] [ k ] + g e t S c o r e ( k , c [ i ] ) ∗ a [ i ] , i = = 1 ∣ ∣ j < i − 1 ( 不换牌 ) d p [ i − 1 ] [ j − 1 ] [ l ] + g e t S c o r e ( k , c [ i ] ) ∗ a [ i ] − b [ j ] , j > 0 & & l ! = k ( 换牌 ) dp[i][j][k]=max\begin{cases} dp[i-1][j][k]+getScore(k, c[i])*a[i], i==1 || j<i-1 (不换牌) \\ dp[i-1][j-1][l]+getScore(k, c[i])*a[i]-b[j], j>0 \&\& l!=k (换牌) \end{cases} dp[i][j][k]=max{ dp[i−1][j][k]+getScore(k,c[i])∗a[i],i==1∣∣j<i−1(不换牌)dp[i−1][j−1][l]+getScore(k,c[i])∗a[i]−b[j],j>0&&l!=k(换牌)
最终结果: max j = 0 ∼ n − 1 , k = 0 ∼ 2 d p [ n ] [ j ] [ k ] \max_{j=0\sim n-1,k=0\sim 2}dp[n][j][k] maxj=0∼n−1,k=0∼2dp[n][j][k]
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int n, a[N], b[N], c[N];
int dp[N][N][3]; //dp[i][j][k]表示游戏进行了i轮,换了j次牌,当前牌为k的最大得分
//我出x,小杨出y,我的得分
int getScore(int x, int y) {
if(x==1 && y==0 || x==2 && y==1 || x==0 && y==2) return 2;
else if(x==y) return 1;
return 0;
}
int main() {
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<n; i++) cin>>b[i];
for(int i=1; i<=n; i++) cin>>c[i];
for(int i=1; i<=n; i++) {
for(int j=0; j<i; j++) {
for(int k=0; k<3; k++) {
int score = getScore(k, c[i])*a[i]; //本轮牌为k的得分
int maxx=-2147483648; //换牌要扣分,因此最大值有可能小于0,因此初值要设的比0小
//本轮不换牌。第一轮不允许换牌,因此前i轮最多有i-1次换牌。dp[i-1][j][k],换牌次数j要小于轮数i-1
if(i==1 || j<i-1) maxx = max(maxx, dp[i-1][j][k]+score);
//本轮换牌
if(j>0) {
//换牌次数j大于0的时候才可能换牌
for(int l=0; l<3; l++) {
//枚举上一轮的牌
//既然换牌,意味着和上一轮牌不同,因此要保证 l!=k
if(l != k) maxx = max(maxx, dp[i-1][j-1][l]+score-b[j]);
}
}
dp[i][j][k] = maxx;
}
}
}
int res=-2147483648;
for(int j=0; j<n; j++) {
for(int k=0; k<3; k++) {
res = max(res, dp[n][j][k]);
}
}
cout<<res;
return 0;
}