CSP-S 初赛复习

· · 算法·理论

点我到博客看

J组暂时没有计划,等5月再整理。

Update 2024/8/2: 加入了在数据结构中增加了“树”,做出部分更改。

Update 2024/8/19:感谢 @daitangchen2008 指出。更改部分错误。

Update 2024/9/20:rp++,更改负数取模错误部分以及树的遍历。

Update 2025/3/15:大幅度调整,修改大量 \LaTeX 错误。重新编辑排版。

Update 2025/4/12:增加例题每,个部分增加 1 至 2 题真题。

Part 1 选择题部分

1 linux基础命令

cd 切换目录
ls 列出目前工作目录所含的文件及子目录。
pwd 显示目前的目录。
mkdir 创建文件夹。
rmdir 删除空文件夹。
touch 创建空白文件。
cp 复制文件或者目录。
rm 删除文件或者目录。
mv 移动文件或者目录。
file 查看文件类型。
man 查看各个命令的使用文档。

[例题1.1][CSP-S2024R1]在 Linux 系统中,如果你想显示当前工作目录的路径,应该使用哪个命令?( )

A. pwd B. cd C. ls D. echo

[解析1.1]由上表可知,pwd 即为列出当前目录,而 ls 为 列出目前工作目录所含的文件及子目录。所以选 A 而不是 B。

[例题1.2][CSP-S2023R1]在 Linux 系统终端中,以下哪个命令用于创建一个新的目录?()

A. newdir B. mkdir C. create D. mkfold

[解析1.2]由上表可知,mkdir 即为创建一个新的目录。所以选 B。

2 Linux time指令

2.1 time 的简单用法

查看命令用时:

[roc@roclinux ~]$ time ls
program public_html repo rocscm

real 0m0.002s
user 0m0.002s
sys 0m0.000s
  1. real从进程 ls 开始执行到完成所耗费的 CPU 总时间

  2. user进程 ls 执行用户态代码所耗费的 CPU 时间

  3. sys进程 ls 在内核态运行所耗费的 CPU 时间

命令的真正执行时间:user_{time}+sys_{time} 的时间

一般情况:real_{time}=user_{time}+sys_{time},所以我们可以使用 real_{time} 作为执行时间。

2.2 time指令深入

情景一:

[roc@roclinux ~]$ time sudo find / -name php.ini

real 0m0.193s
user 0m0.076s
sys 0m0.115s

证明:

$\therefore \ real_{time} > user_{time}+sys_{time}$ 是非常有可能的。 - **不一定 $real_{time}>user_{time}+sys_{time}$。** *证明:* $\because$ 多核CPU可以处理多项事务。 $\therefore$ 完成工作总花费时间为:$user_{time}+sys_{time}$。 存在两种情况: 1. $real_{time}=user_{time}+sys_{time}
  1. real_{time}<user_{time}+sys_{time}
- **不一定 $real_{time}<user_{time}+sys_{time}$。** 单核 CPU 中关系不成立。 情景二: 第一次执行: ```plain [roc@roclinux ~]$ time sudo find / -name mysql.sh /etc/profile.d/mysql.sh real 0m6.776s user 0m1.101s sys 0m1.363s ``` 第二次执行: ```plain [roc@roclinux ~]$ time sudo find / -name mysql.sh /etc/profile.d/mysql.sh real 0m3.059s user 0m1.189s sys 0m1.435s ``` 原因:对于运行时间较短的任务计时时,会产生一定误差。`time` 命令输出的时间统计精度基本在 $10$ 毫秒级。 >**[例题2.1][CSP-S2022R1]你同时用 time 命令和秒表为某个程序在单核 CPU 的运行计时。假如 time 命令的输出如下:** > ```plain > real 0m30.721s > user 0m24.579s > sys 0m6.123s > ``` > **以下最接近秒表计时的时长为( )。** > > A. 30s B. 24s C. 18s D. 6s > >**[解析2.1] 依题意可得: $time=user_{time}+sys_{time}=24.579s+6.123s=30.702s\approx30s$,或 $time=real_{time}=30.721s\approx30s$,所以选 A。** --- ## 3 G++ 编译选项 | 指令 | 含义 |---------|----------------| | `-c` | 编译源代码,但不进行链接操作,生成目标文件。 | | `-o` | 指定输出文件名。例如,`-o myprogram` 表示将输出文件命名为 `myprogram`。 | | `-g` | 生成调试信息。这意味着编译器将在目标文件中包含调试信息,可以用于调试程序。 | | `-O` | 指定优化级别。例如,`-O2` 表示使用较高的优化级别。 | | `-Wall` | 生成所有警告信息。这意味着编译器将生成所有警告信息,帮助开发者检查代码。 | | `-std=` | 指定使用的 C/C++ 标准。例如,`-std=c++11` 表示使用 C++11 标准。 | | `-I` | 指定编译时搜索的头文件目录。 | | `-D` | 定义宏。例如,`-DDEBUG` 表示定义宏 `DEBUG`。 | | `-U` | 取消定义宏。例如,`-UDEBUG` 表示取消定义宏 `DEBUG`。 | | `-E` | 只进行预处理操作,不进行编译和链接操作。 | | `-Werror` | 将所有警告信息视为错误信息。这意味着编译器将在生成警告信息时停止编译操作。 | > **[例题3.1][CSP2023R1]以下哪个命令,能将一个名为 main.cpp 的 C++ 源文件,编译并生成一个名为 main 的可执行文件?()** > > A. `g++ -o main main.cpp` B. `g++ -o main.cpp main` C. `g++ main -o main.cpp` D. `g++ main.cpp -o main.cpp` > > **[解析3.1]编译通常格式是 `gcc/g++ -o 可执行文件名 源文件`,故选 A。** >**[例题3.2]在用 gcc 进行编译的时候,如果想要只激活预处理、编译、汇编,那在输入编译选项的时候需要选择( )。** > > A. `-S` B.`-c` C.`-E` D.`-o` > > **[解析3.2]由上表可得 `-o` 指指定输出文件名,即激活预处理、编译、汇编,故选 D。** --- ## 4 进制转换 ### 4.1 进制数初步认识 1. **十进制: 都是以 $0-9$ 这九个数字组成,不能以 $0$ 开头。** 2. **二进制: 由 $0$ 和 $1$ 两个数字组成。例如 $(10110)_{2}$。** 3. **八进制: 由 $0-7$ 数字组成,例如 $(57)_{8}$。** 4. **十六进制:由 $0-9$ 和 $A-F$(可以为小写) 组成。例如 $(7F)_{16}$。** ### 4.2 十进制转 X 进制: - #### 4.2.1 整数转换 1. 将需要转换的数 $a$ 除以 $x$,取余 $a \bmod x$,余数为所求进制数,从下往上取 2. 所得整数部分保留,重复步骤 $1$,直到商为 $0$。 例如:$9_{(10)}\to1001_{(2)}

​ 

十进制转二进制:

​ 原理:十进制小数转换成二进制小数采用 “乘2取整,顺序输出” 法。

例如:十进制小数 0.68 转换为二进制数。 具体步骤:

0.68\times 2=1.36\to1 \\ 0.36\times 2=0.72 \to0 \\ 0.72\times2=1.44 \to1 \\ 0.44\times2=0.88\to0 \\ 0.88\times2=1.76\to1 \\

已经达到了题目要求的精度,最后将取出的整数部分顺序输出即可。 则为:(0.68)_{10} \to (0.10101)_2

​ 其他进制思路一样小数与整数结合的,两种方法直接一起套用

4.3 X 进制转换为十进制

小数部分:小数部分从小数点后一位指数 -1 为开始算起,以后依次为 -2-3,以此类推。

5 排序算法

6 原码、补码、反码、计算

6.1 机器数和真值

6.1.1 机器数

一个数在计算机中的二进制表示形式,  叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为 0, 负数为 1

比如,十进制中的数 +3,计算机字长为 8 位,转换成二进制就是 (00000011)_2。如果是 -3 ,就是 (10000011)_2

那么,这里的 (00000011)_2(10000011)_2 就是机器数。

6.1.2 真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 (10000011)_2,其最高位 1 代表负,其真正数值是 -3 而不是形式值 131(10000011)_2转换成十进制等于 131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

例:(00000001)_2 = (+0000001)_2 = (+1)_{10} \\(10000001)_2 = (–0000001)_2 = (–1)_{10}

6.2 原码, 反码, 补码的基础概念和计算方法

6.2.1 原码

原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如如果是 8 位二进制:

+1 = (00000001)_2 \\ -1 = (10000001)_2

第一位是符号位。因为第一位是符号位,所以 8 位二进制数的取值范围就是:

11111111 \Rightarrow 01111111

-127 \Rightarrow 127

原码是人脑最容易理解和计算的表示方式。

6.2.2 反码

反码的表示方法是:

正数的反码是其本身

负数的反码是在其原码的基础上,符号位不变,其余各个位取反。

+1 = (00000001)_2 \to (00000001)_2 \\ -1 = (10000001)_2 \to (11111110)_2

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。

6.2.3 补码

补码的表示方法是:

正数的补码就是其本身

负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后 +1。(即在反码的基础上 +1。)

+1 = (00000001)_2 \to (00000001)_2 \to (00000001)_2 \\ -1 = (10000001)_2 \to (11111110)_2 \to (11111111)_2

对于负数, 补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码在计算其数值。

7 同余

7.1 同余的概念

两个整数 ab,若它们除以整数 m 所得的余数相等,则称 ab 对于模 m 同余。

记作 a \equiv b \pmod m

读作: ab 关于模 m 同余。

举例说明:

4 \bmod 12 = 4 \\ 16 \bmod 12 = 4 \\ 28 \bmod 12 = 4 \\

所以 4, 16, 28 关于模 12 同余。

7.2 负数取模

可参考 C99 标准:

取模运算结果的正负是由左操作数的正负决定的。如果 % 左操作数是正数,那么取模运算的结果是非负数;如果 % 左操作数是负数,那么取模运算的结果是负数或0。

代码实践:

#include <iostream>
using namespace std;
int main()
{
    cout << 5 % 2 << endl;
    cout << 5 % -2 << endl;
    cout << -5 % 2 << endl;
    cout << -5 % -2 << endl;
    return 0;
}

输出结果:

1
1
-1
-1

8 运算符

按位与 &
参加运算的两个数,换算为二进制后,进行与运算。只有当相应位上的数都是 1 时,该位才取 1,否则该位为 0

按位或 |
参加运算的两个数,换算为二进制后,进行或运算。只要相应位上存在 1,那么该位就取 1,均不为 1,即为 0

按位异或 ^
参加运算的两个数,换算为二进制后,进行异或运算。只有当相应位上的数字不相同时,该为才取 1,若相同,即为 0

任何数与 0 异或,结果都是其本身。

异或还可以交换两个数

a = a ^ b;
b = b ^ a;
a = a ^ b;

取反 ~
参加运算的两个数,换算为二进制后,进行取反运算。每个位上都取相反值,1 变成 00 变成 1

左移 <<

x<<n = x\times2^n \\ 5<<2=101_{(2)}<<2=10100_{(2)}=20=5\times2^2

右移 >>

x>>n = \lfloor\frac{x}{2^n}\rfloor \\ 5>>1=101_{(2)}>>1=10_{(2)} =2=\lfloor5\div2^1\rfloor \\

9 大端与小端模式

前置知识:读数据永远是从低地址开始的。

9.1 低地址、高地址

地址编号小的是低地址,地址编号大的是高地址。

9.2 数据的低位、高位

9.3 小端模式

小端模式:数据的 低位 放在 低地址 空间,数据的 高位 放在 高地址 空间。

简记:小端就是低位对应低地址,高位对应高地址

存放二进制数:1011-0100-1111-0110-1000-1100-0001-0101

注意:我们在存放的时候是以一个存储单元为单位来存放,存储单元内部不需要再转变顺序。 就例如下面的低位 0001-0101 存放在 0 号地址,我们不需要把它变成 1010-1000

读取数据:注意一定一定是从低地址读起。我们知道这是小端存储,所以在读出来的时候会从低位开始放

存放十六进制数:(2AB93584FE1)_{16}
十六进制数每一位转化为二进制就是 4 位:2 对应 0010A 对应 1010,以此类推。所以在存放的时候两个十六进制位就占用一个存储单元。

9.4 大端模式

数据的 高位 放在 低地址 空间,数据的 低位 放在 高地址空间

存放二进制数:1011-0100-1111-0110-1000-1100-0001-0101

读取数据:注意仍然是从低地址开始读,我们知道这是大端模式,当我们从 0 号地址读到 1011-0100 时,我们知道它是高位,所以放到高位的位置上去

存放十六进制数:(2AB93584FE1C)_{16}

读取数据:注意从低地址开始读取,读到的从高地址开始放

10 数据结构

10.1 基础数据结构

10.2 拓展数据结构

11 组合数学

本篇文章根据 NOI 大纲(2023年修订版) 以及根据自己的做题经验编写,也参考了OI wiki以及 CSDN 和 博客园 的教程,欢迎大家指正。