CSP-S 2024 第一轮 · 客观题精讲讲义

试卷:CSP 2024 提高级(Senior)第一轮 · 单项选择 + 阅读程序 + 完善程序

用法:先独立作答,再点「显示解析」核对分步推理;右上角可切换 浅色/深色 配色。

单选题 15 阅读程序 3 段 完善程序 2 段 共 42 个作答点 答案均经代码验证

🗺️ 本卷知识地图

CSP-S 提高级第一轮,难度较高。本卷可归为 8 大板块,阅读/完善程序是拉开分差的重点。

① 系统与语言

Q1 Linux 命令、Q3 递归导致栈溢出。

② 复杂度

Q2 无序找最大 O(n)、Q10 哈希最坏 O(n)、Q11 完全二叉树结点数。

③ 数据结构与查找

Q5 队列 FIFO、Q8 二分前提有序。

④ 图论

Q7 欧拉图性质、Q15 最小割计数。

⑤ 组合数学

Q4 排列、Q6 递推、Q12 完全图中 4 元环、Q13 数位和、Q14 相邻交换次数。

⑥ 数论

Q9 乘法逆元(扩展欧几里得)。

⑦ 阅读程序 ⭐

位运算 + 快排(带深度)、子序列价值 DP / 折半、二叉树哈希(中序合并 + 筛法)。

⑧ 完善程序 ⭐

二分答案求第 K 小的和、分层图 Dijkstra 求严格次短路。

第一部分 · 单项选择题(1–15)
第 1 题单选Linux 命令

在 Linux 中,显示当前工作目录的路径,应使用哪个命令?

第 2 题单选复杂度

长度为 n、元素互不相同且无序的数组,找最大元素的时间复杂度是?

第 3 题单选栈溢出/递归

以下哪个函数调用会造成栈溢出?

第 4 题单选排列

10 名选手,前三名颁金、银、铜牌(不并列、每人最多一枚),共多少种颁奖方式?

第 5 题单选FIFO

最适合实现先进先出(FIFO)的数据结构是?

第 6 题单选递推

f(1)=1,n≥2 时 f(n)=f(n−1)+f(⌊n/2⌋),求 f(4)。

第 7 题单选欧拉图

n 个顶点的无向图是欧拉图,下列描述中哪项不一定正确?

第 8 题单选二分查找

二分查找过程中,以下哪个条件必须满足?

第 9 题单选乘法逆元

计算 n 在模 m 意义下的乘法逆元,下列哪种算法最为适合(通用)?

第 10 题单选哈希·开放地址

哈希表用开放地址法解决冲突,装载因子 α,最坏情况下查找一个元素的时间复杂度为?

第 11 题单选完全二叉树

一棵 h 层的完全二叉树,最多包含多少个结点?

第 12 题单选完全图中的环

10 个顶点的完全图,有多少个长度为 4 的环?

第 13 题单选数位和

f(n) 为 n 各位数字之和,求使 f(f(x))=10 的最小自然数 x。

第 14 题单选相邻交换

长度 n 的 01 串含 k 个 1,每次交换相邻两字符。最坏情况下把 k 个 1 全移到最右边所需的交换次数是?

第 15 题单选最小割计数

下图是 7 个顶点的有向图。删除最少的边使 1 到 7 没有可行路径,问这样的「最少删边集合」共有多少种?

Q15 有向图

第二部分 · 阅读程序

📄 阅读程序(一)· 位运算 + 带深度限制的快速排序

logic 是某种位运算;generate 用它造数组;recursion 是带「深度」参数的快速排序。

int logic(int x, int y){ return (x & y) ^ ((x ^ y) | (~x & y)); }
void generate(int a, int b, int *c){
    for (int i = 0; i < b; i++) c[i] = logic(a, i) % (b + 1);
}
void recursion(int depth, int *arr, int size){     // 快排,depth 限制递归层数
    if (depth <= 0 || size <= 1) return;
    int pivot = arr[0]; int i = 0, j = size - 1;
    while (i <= j){
        while (arr[i] < pivot) i++;
        while (arr[j] > pivot) j--;
        if (i <= j){ swap(arr[i], arr[j]); i++; j--; }
    }
    recursion(depth-1, arr, j+1);
    recursion(depth-1, arr+i, size-i);
}
// main: 读 a b d; generate(a,b,c); recursion(d,c,b); 输出 c[0..b-1]
16-判1判断

当 1000 ≥ d ≥ b 时,输出的序列是有序的。

16-判2判断

当输入 5 5 1 时,输出为 1 1 5 5 5

16-判3判断

假设数组 c 长度无限制,该程序所实现的算法的时间复杂度是 O(b)。

16-选1单选

函数 logic(int x, int y) 的功能是?

16-选2单选4 分

当输入为 10 100 100 时,输出的第 100 个数是?

📄 阅读程序(二)· 子序列「数值之和」(DP 与折半暴力)

s 是长 n 的 01 串。solve2() 暴力枚举所有「长度 ≤ m 的子序列」,把每个子序列按二进制读成数值并求和。solve() 用 dp[mask] 统计「数值等于 mask 的子序列个数」再求 Σ mask·dp[mask],得到同样的和(mod 998244353)。

const int P = 998244353;
int dp[1<<M];
int solve(){                          // dp 法:O(n·2^m)
    dp[0] = 1;
    for (int i = 0; i < n; ++i)
      for (int j = (1<<(m-1))-1; j >= 0; --j){
        int k = (j<<1) | (s[i]-'0');
        if (j != 0 || s[i]=='1') dp[k] = (dp[k] + dp[j]) % P;  // 避免前导 0
      }
    int ans = 0;
    for (int i = 0; i < (1<<m); ++i) ans = (ans + 1ll*i*dp[i]) % P;
    return ans;
}
int solve2(){                         // 暴力枚举子集:O(2^n·n),仅 n≤20 用
    int ans = 0;
    for (int i = 0; i < (1<<n); ++i){
        int cnt=0, num=0;
        for (int j=0;j<n;++j) if (i&(1<<j)){ num=num*2+(s[j]-'0'); cnt++; }
        if (cnt <= m) (ans += num) %= P;
    }
    return ans;
}
// main: 读 n m s; 若 n≤20 先输出 solve2(),再输出 solve()
17-判1判断

假设 dp 长度无限制,solve() 的时间复杂度是 O(n×2m)。

17-判2判断

输入 11 2 10000000001 时,程序输出两个数 32 和 23。

17-判3判断2 分

在 n ≤ 10 时,solve() 的返回值始终小于 410

17-选1单选

当 n=10 且 m=10 时,有多少种输入(01 串)使两行结果完全一致?

17-选2单选

当 n ≤ 6 时,solve() 的最大可能返回值为?

17-选3单选

若 n=8, m=8,solve 与 solve2 返回值的最大可能差值为?

📄 阅读程序(三)· 二叉树哈希(中序合并 + 埃氏筛)

把下标 1..n 看成二叉树(i 的左右孩子是 2i、2i+1)。p[i] 标记 i 是否为素数,H 是双模哈希。h[i]=h[2i]+h[i]+h[2i+1](左、根、右)自底向上合并,输出 h[1].h1 和不同哈希的个数。

struct H { int h1,h2,l; ...
    H operator+(const H& b) const {        // 拼接两段哈希
        H r; r.l=l+b.l;
        r.h1=(1ll*h1*p1[b.l]+b.h1)%P1;
        r.h2=(1ll*h2*p2[b.l]+b.h2)%P2; return r; }
} h[maxn];
void init(){ /* 埃氏筛求素数 p[] + 预处理幂 p1[],p2[] */ }
int solve(){
    for (int i=n;i;--i){
        h[i]=H(p[i]);
        if (2*i+1<=n) h[i]=h[2*i]+h[i]+h[2*i+1];   // 左 + 根 + 右 = 中序
        else if (2*i<=n) h[i]=h[2*i]+h[i];
    }
    cout<<h[1].h1<<endl;                  // 第一行
    sort(h+1,h+n+1);
    return unique(h+1,h+n+1)-(h+1);      // 第二行:不同哈希个数
}
18-判1判断

若能自动把 maxn 改为 n+1,所实现算法的时间复杂度是 O(n log n)。

18-判2判断

时间开销的瓶颈是 init() 函数。

18-判3判断

若修改常数 B1 或 K1 的值,程序可能会输出不同的结果。

18-选1单选

solve() 中 h[] 的合并顺序可以看作是?

18-选2单选

输入 10,输出的第一行是?

18-选3单选

输入 16,输出的第二行是?

第三部分 · 完善程序(单选)

🧩 完善程序(一)· 求两序列两两之和的第 K 小

单调不降序列 A、B 各取一数相加得 N² 个和,求第 K 小。做法:二分答案 sum,用 get_rank(sum)=「和 ≤ sum 的对数」判断;get_rank 内对每个 a[i] 用 upper_bound 统计 b 中 ≤ sum−a[i] 的个数。补全 ①~⑤。

int* upper_bound(int *a, int *an, int ai){   // 返回第一个 > ai 的位置
    int l = 0, r = __①__;
    while (l < r){ int mid=(l+r)>>1;
        if (__②__) r = mid; else l = mid + 1; }
    return __③__;
}
long long get_rank(int sum){
    long long rank = 0;
    for (int i=0;i<n;++i) rank += upper_bound(b, b+n, sum-a[i]) - b;
    return rank;                              // 和 ≤ sum 的对数
}
int solve(){                                 // 二分最小的 sum 使 rank(sum) ≥ k
    int l = 0, r = __④__;
    while (l < r){ int mid = ((long long)l+r)>>1;
        if (__⑤__) l = mid + 1; else r = mid; }
    return l;
}
19-①完善

① 处应填?

19-②完善

② 处应填?

19-③完善

③ 处应填?

19-④完善

④ 处(二分答案的右端)应填?

19-⑤完善

⑤ 处应填?

🧩 完善程序(二)· 严格次短路(分层图 Dijkstra)

把每个点拆成两层:下标 v(0~n−1)存最短路,下标 n+v 存严格次短路。用 Dijkstra 同时维护。upd(a,b,d,q) 尝试用 d 更新 dis[b],更新成功时若 b 是最短层(b<n),把被顶替的旧最短路下放到次短层。补全 ①~⑤。

const int inf = 522133279;       // = 0x1f1f1f1f
bool upd(int a,int b,int d, pq& q){
    if (d >= dis[b]) return false;
    if (b < n) __①__;          // 旧最短路下放到次短层
    q.push(__②__);             // 大根堆存 -d 模拟小根堆
    dis[b]=d; pre[b]=a; return true;
}
void solve(){
    pq q; q.push(make_pair(0,s));
    memset(dis, __③__, sizeof(dis));   // 填 inf
    memset(pre,-1,sizeof(pre));
    dis2=dis+n; pre2=pre+n; dis[s]=0;
    while(!q.empty()){
        int aa=q.top().second; q.pop();
        if (vis[aa]) continue; vis[aa]=true;
        int a=aa%n;
        for (int e=head[a]; e; e=nxt[e]){
            int b=to[e], c=w[e];
            if (aa < n){
                if (!upd(a,b,dis[a]+c,q)) __④__;   // 最短层更新失败 → 尝试次短层
            } else upd(n+a, n+b, dis2[a]+c, q);
        }
    }
}
void out(int a){                 // 递归打印次短路方案
    if (a != s){ if (a<n) out(pre[a]); else out(__⑤__); }
    printf("%d%c", a%n+1, " \n"[a==n+t]);
}
20-①完善

① 处应填?

20-②完善

② 处应填?

20-③完善

③ 处(memset 填充 inf)应填?

20-④完善

④ 处(最短层更新失败后)应填?

20-⑤完善

⑤ 处(次短层节点回溯前驱)应填?