CSP-J1S1_2025

考点小记

  1. 等比数列求和公式
    已知等比数列 \(\{a_n\}\) ,公比为 \(q\),前 \(n\) 项和为 \(S_n\)
    则有 \(S_n = \begin{cases} na_1, &q = 1 \\ \large \frac{a_1(1 - q ^ n)}{1 - q}, &q \neq 1 \end{cases}\)
    证明:当 \(q \neq 1\) 时,\(\because S_n = a_1 + a_2 + a_3 + \cdots + a_n\)
    \(\therefore S_n = a_1 + a_1q + a_1q ^ 2 + a_1q ^ 3 + \cdots + a_1q^{n - 1}\)
    \(\therefore qS_n = a_1q + a_1q ^ 2 + a_1q ^ 3 + a_1q ^ 4 + \cdots + a_1q ^ n\)
    \(\therefore (1 - q)S_n = a_1(1 - q ^ n)\)
    \(\therefore S_n = \large \frac{a_1(1 - q ^ n)}{1 - q}\)

  2. 对数化幂为积公式
    例:若一个自然数在十进制下有 \(n\) 位,则它在二进制下的位数与哪个表达式最接近?
    解:设该数在二进制下的位数为 \(x\) 位,则近似有
    \(\begin{aligned} 2 ^ x &= 10 ^ n \\ x &= \log_2 10 ^ n \\ x &= n \log2_2 10 ^ n \end{aligned}\)

  3. 对于边权为正的图执行 Dijkstra 算法,使用斐波那契堆数据结构效率最高。
    斐波那契堆各操作的时间复杂度:插入、合并、查找最小节点均摊 \(O(1)\) ,删除最小节点平摊 \(O(\log n)\)
    斐波那契堆的空间消耗较大,时间复杂度一般优于二叉堆。

  4. 二叉搜索树(二叉排序树)支持的操作
    插入、删除、求第 \(k\) 大的元素、求元素 \(x\) 是第几大的元素。

  5. 巧用完全二叉树的编号性质
    例:若一棵完全二叉树有 100 个结点,则其叶子结点的个数为?
    解:由完全二叉树的编号性质得,若父结点的编号为 \(i\) ,则左儿子结点的编号为 \(2i\) ,右儿子结点的编号为 \(2i + 1\)
    显然,编号为 50 的结点的左儿子结点的编号为 100 。所以编号为 51 ~ 100 的结点均为叶子结点。
    故叶子结点的个数 \(n_0 = 100 - 51 + 1 = 50\)

  6. 用动态规划求解“最长公共子序列”(LCS)问题时,若两个字符串 \(s\)\(t\) 的长度分别为 \(m\)\(n\) ,则所需的存储空间为 \(O(mn)\)
    \(f_{i, j}\) 表示字符串 \(s\)\(i\) 位,字符串 \(t\)\(j\) 位的最长公共子序列的长度。

  7. DFS 和 BFS 的时间复杂度均为 \(O(n + e)\) ,DFS 不仅可以用递归实现,还可以用来模拟递归过程实现。

  8. 拓扑(pū)排序
    拓扑排序可用于判断有向图是否存在环,对有向无环图(DAG)的所有结点排序,确定一个依赖关系集合中事物的发生顺序等。
    以下是一个时间复杂度为 \(O(n + m)\) 的拓扑排序的代码。

int ind[MAXN], n, m;
vector<int> e[MAXN];
bool topo_sort() {
	queue<int> q;
	int cnt = 0;
	for (int i = 1; i <= n; i++) 
		if (!ind[i]) q.push(i);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		cnt++;
		for (int v : e[u])
			if (!--ind[v]) q.push(v);
	}
	return cnt == n;
}
  1. 快速排序
    快速排序采用分治的策略,递归地执行。平均时间复杂度 \(O(\log n)\) ,最坏情况下可达到 \(O(n ^ 2)\)
    基本思想:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。
    然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
void qsort(int a[], int l, int r) {
	int i = l, j = r, flag = a[l + r >> 1]; // flag 须保证是在 [l, r] 范围内的数据
	while (i <= j) {
		while (a[i] > flag) i++;
		while (a[j] > flag) j--;
		if (i <= j) swap(a[i], a[j]), i++, j--;
	}
	if (l < j) qsort(a, l, j);
	if (i < r) qsort(a, i, r);
}
  1. 计算机基础知识
  • 内存储器通常可以分为随机存储器(RAM),只读存储器(ROM)和高速缓冲存储器(Cache)。内存(主存)和 CPU 构成了计算机的主机部分。
  • 一般的个人计算机在同一时刻只能存取一个特定的内存单元。
  • 分时系统让多个用户可以共享一台主机的运算能力,为保证每个用户都能得到及时的响应通常会采用时间片轮转调度的策略。
  1. 主定理计算时间复杂度
    主定理用于解决形如以下形式的递归(分治)算法的时间复杂度。
    \(T(n) = aT(n / b) + f(n)\)
    其中 \(a(a \geq 1)\) 为子问题个数, \(b(b > 1)\) 为子问题规模。
    则答案有以下三种情况。
  • 若函数 \(n ^ {\log_b a}\) 更大,则 \(T(n) = O(n ^ {\log_b a})\)
  • 若函数 \(f(n)\) 更大,且 \(af(n / b) < cf(n)\) ,则 \(T(n) = O(f(n))\)
  • 若两函数相等,则 \(T(n) = O(n ^ {\log_b a} \log ^ {k + 1} n)\)

即比较 \(n ^ {\log_b a}\)\(f(n)\) 的大小,取较大的。若相等则加上 \(\log n\) 的时间复杂度。
例1:设某算法的计算时间表示为递推关系式 \(T(n) = 2T(n / 2) + n (n \in N ^ +)\) ,且 \(T(0) = 1\) ,则该算法的时间复杂度为?
解:\(\because a = 2, b = 2, O(n ^ {\log_2 2}) = \Theta(n) = O(f(n))\)
\(\therefore T(n) = O(n \log n)\) 符合 Case 3 。
例2:设某算法的计算时间表示为递推关系式 \(T(n) = T(n - 1) + 2n (n \in N ^ +)\) ,且 \(T(0) = 0\) ,则该算法的时间复杂度为?
解:本题的递推方程属于线性递推(非分治型),不能使用主定理。考虑累加递推求解。
\(\begin{aligned} T(n) &= T(n - 1) + 2n \\ &= [T(n - 2) + 2(n - 1)] + 2n \\ &\vdots \\ &= T(0) + 2 \times 1 + 2 \times 2 + \dots + 2n \\ &= 2(1 + 2 + \dots + n) \\ &= 2 \times \frac{n(n + 1)}{2} \\ &= n(n + 1) \\ &= O(n ^ 2) \end{aligned}\)

  1. 插入排序的平均时间复杂度为 \(O(n ^ 2)\)

  2. 一定可以进行黑白染色,使得相邻结点的颜色不同。基环树、连通图、欧拉图均不一定。

  3. 整数划分(EquationCount)问题
    整数划分是将正整数 \(n\) 划分成多个不小于 1 的整数之和的组合形式。不同顺序的加数视为同一划分。
    \(f(n, m)\)\(n\) 的最大加数不超过 \(m\) 的划分个数。
    \(f(n, m) = \begin{cases} 1, & n = 1 \lor m = 1 \\ f(n, n), & n < m \\ 1 + f(n, n - 1), & n = m \\ f(n - m, m) + f(n, m - 1), & n > m \end{cases}\)
    对于正整数 \(n\) ,其划分数为 \(f(n, n)\)
    以下代码的时间复杂度为 \(O(2 ^ n)\)

int equation_count(int n, int m) {
	if (n == 1 || m == 1) return 1;
	if (n < m) return equation_count(n, n);
	if (n == m) return 1 + equation_count(n, n - 1);
	return equation_count(n - m, m) + equation_count(n, m - 1);
}
  1. 最长上升子序列(LIS)问题
    最长上升子序列是指元素按顺序递增且长度最大的子序列。这里考虑贪心和二分优化动态规划解法的 \(O(n ^ 2)\)\(O(nlogn)\)
    \(b_i\) 表示长度为 \(i + 1\) 的上升子序列的末尾元素的最小值。
int search(int lf, int rt, int x) {
	int mid;
	while (lf <= rt) {
		mid = lf + rt >> 1;
		if (x >= b[mid]) lf = mid + 1;
		else rt = mid - 1;
	}
	return lf;
} // search() 等价于 lower_bound() 
b[1] = a[1];
int len = 1;
for (int i = 2; i <= n; i++) {
	if (a[i] > b[len]) b[++len] = a[i];
	else if (a[i] < b[len]) {
		int pos = lower_bound(a + 1, a + len + 1, a[i]) - a;
		// pos = search(1, len, a[i]);
		b[pos] = a[i];
	}
}
  1. 树的直径
    树的直径是树上两点间距离的最大值,即树上最长的不重复经过一个点的路径长度。连接这两点之间的路径被称为树的最长链。
    显然,一棵树可以有多条直径,其长度相等。

从树上任意一个点出发所到达的最远的点一定是树的两个端点之一。

考虑用反证法证明树的直径的这条性质。
证明:记两点 \(u\)\(v\) 间的距离为 \(d(u,v)\) 。设树的最长链为 \((s, t)\) ,出发结点为 \(y\) ,搜索到达最远结点 \(z\) 不为 \(s\)\(t\) ,则树的直径是 \(d(s, t)\)

  • \(y\)\((s, t)\) 上,

\(d(y, z) > d(y, t) \Longrightarrow d(x, z) > d(x, t) \Longrightarrow d(s, z) > d(s, t)\) ,与 \(d(s, t)\) 为树的直径矛盾。

  • \(y\) 不在 \((s, t)\) 上,且 \((y, z)\)\((s, t)\) 存在重合路径。

\(d(y, z) > d(y, t) \Longrightarrow d(x, z) > d(x, t)\Longrightarrow d(s, z) > d(s, t)\) ,与 \(d(s, t)\) 为树的直径矛盾。

  • \(y\) 不在 \((s, t)\) 上,且 \((y, z)\)\((s, t)\) 不存在重合路径。

\(d(y, z) > d(y, t) \Longrightarrow d(x', z) > d(x', t) \Longrightarrow d(x, z) > d(x, t) \Longrightarrow d(s, z) > d(s, t)\) ,与 \(d(s, t)\) 为树的直径矛盾。

综上,三种情况下假设均会产生矛盾,故该性质得证。
不难发现,可以用两次 DFS 或 BFS 的方法在 \(O(n)\) 的时间复杂度求得树的直径。
具体地,首先从任意点 \(y\) 开始进行第一次搜索 ,到达距离其最远的点 \(z\) ,然后再从点 \(z\) 开始做第二次搜索 ,到达距离 \(z\) 最远的点 \(z'\) ,则 \(d(z, z')\) 即为树的直径。

vector<int> e[MAXN];
int d[MAXN], s, n;
int q[MAXN], f[MAXN], hd, tl, t;
void dfs(int u, int f) {
	for (int v : e[u]) {
		if (v == f) continue;
		d[v] = d[u] + 1;
		if (d[v] > d[s]) s = v;
		dfs(v, u);
	}
}
int bfs(int u) {
	memset(f, false, sizeof(f));
	hd = tl = 0, q[tl++] = u, d[u] = 0;
	while (hd < tl) {
		u = q[hd++];
		for (int v : e[u])
			if (!f[v])
				q[tl++] = v, d[v] = d[u] + 1, f[v] = true;
	}
	int c = 1;
	for (int i = 2; i <= n; i++)
		if (d[c] < d[i]) c = i;
	return c;
}
int main() {
	cin >> n;
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].push_back(v), e[v].push_back(u);
	}
	dfs(1, 0);
    d[s] = 0, dfs(s, 0);
	cout << d[s];
/*
    s = bfs(1);
    t = bfs(s);
    cout << d[t];
*/
	return 0; 
}
  1. 完全二叉树的结点数量关系
    设一完全二叉树有 \(N\) 个结点,$ n_0 $叶结点。
    \(n_0 = \begin{cases} \large \frac{N + 1}{2}, & N \quad is \quad odd \\ \large \frac{N}{2}, & N \quad is \quad even \end{cases}\)
    例:若一完全二叉树有 \(4n + 1\) 个结点,求叶结点的数量?
    解:\(\because N = 4n + 1\) 是奇数 \(\therefore n_0 = \frac{4n + 1 + 1}{2} = 2n + 1\)

  2. 按照字典序排序的下一个排列

  • 从左往右找到第一个减小的数字 \(a\)
  • \(a\) 起往右找第一个比 \(a\) 大的最小数字 \(b\)
  • 交换 \(a\)\(b\) ,将交换后的的 \(b'\) 右边从小到大排序。
  1. 链式前向星存图
    \(head[u]\) 表示以 \(u\) 为起点的第一条边的编号。
    \(to[i]\) 表示第 \(i\) 条边所指向的点。
    \(nxt[i]\) 表示第 \(i\) 条边下一条边的编号。
    \(val[i]\) 表示第 \(i\) 条边的权值。
int nxt[MAXM], to[MAXM], val[MAXN], head[MAXN], cnt;
void add(int u, int v, int w) {
    nxt[++cnt] = head[u];
    to[cnt] = v;
    val[cnt] = w;
    head[u] = cnt;
}
  1. \(n\) 个结点构成的形态不同的二叉树数量为对应的卡特兰数。
    \(Catalan(n) = \large \frac{C_{2n}^n}{n+1} \small (n \in N)\)
  2. Linux 系统的基本命令
  • ls:列出目录内容。
  • cd:更改当前工作目录。
  • pwd:显示当前工作目录的路径。
  • mkdir:创建新目录。
  • rmdir:删除空目录。
  • rm:删除文件或目录。
  • cp:复制文件或目录。
  • mv:移动或重命名文件或目录。
  • touch:创建空文件或更新文件的时间戳。
  • find:在文件系统中查找文件和目录。

-r:目录; -f:强制。

  1. C++ 运算符优先级
    关系运算符 \(>\) 运算(移位除外)\(>\) 逻辑运算
    例:以下 C++ 代码运行后 \(t\) 的值为?
    解:auto 类型会自适应为 int 类型,答案为 \(0\)
int a = 1, b = 1, c = 4, d = 5, e = 1;
bool f = true;
auto t = ((a > b ^ c) && f) < d ^ e;

错题整理

J 组

单项选择

  1. 一个 32 位无符号整数可以表示的最大值,最接近以下哪个选项?(A
    A \(4\times10^9\) B \(3\times10^{10}\) C \(2\times10^9\) D \(2\times10^{10}\)

  2. 用 5 个权值 10,12,15,20,25 构造哈夫曼树,该树的带权路径长度是多少?(B
    A 176 B 186 C 196 D 206
    构造哈夫曼树,带权路径长度为各个权值与对应的深度之和。即 \((10 + 12) \times 3 + (25 + 15 + 20) \times 2 = 186\) .

  3. 已知 f[0] = 1, f[1] = 1, 并且对于所有 n≥2 有 f[n] = (f[n-1] + f[n-2]) % 7.那么 f[2025] 的值是多少?(D
    A 2 B 4 C 5 D 6
    模拟前几项,{1, 1, 2, 3, 5, 1, 6, 0, 6, 6, 5, 4, 2, 6, 1, 0} 发现循环节为 16 。 则 \(f[2025] = f[2025 \mod 16] = f[9] = 6\)

  4. 下列关于 C++ string类的说法,正确的是?(B
    A string 对象的长度在创建后不能改变。 可以改变
    B 可以使用 + 运算符直接连接一个 string 对象和一个 char 类型的字符。
    C string 的 length() 和 size() 方法返回的值可能不同。 size() 等价于 length()
    D string 对象必须以 '\0' 结尾, 且这个结尾符计入 length()。 '\0' 不计入 length()

  5. 一个 8×8 的棋盘, 左上角坐标为 (1,1), 右下角为 (8,8)。一个机器人从 (1,1) 出发,每次只能向右或向下走一格。要到达 (4,5),有多少种不同的路径?(B
    A 20 B 35 C 56 D 70
    注意到,路径数为 \(C_{(4 - 1) + (5 - 1)}^{4 - 1} = C_7^3 = 35\)

  6. 一棵包含 1000 个结点的完全二叉树,其叶子结点的数量是多少?(C
    A 499 B 512 C 500 D 501
    本题极其不应该失误。\(n_0 = \lceil \frac{1000}{2} \rceil = 500\)

阅读程序

T1
  1. 将第 16 行中的“&&gcd(i,k)==1”删去不会影响程序运行结果。(F
    反例 \(\left\{ i = 2, j = 3, k = 4 \right\}\)

  2. 将第 7 行的“gcd(b,a%b)”改为“gcd(a,a%b)”后,程序可能出现的问题是?(B
    A 输出的答案大于原答案。
    B 输出的答案小于原答案。
    C 程序有可能陷入死循环。
    D 可能发生整型溢出问题。
    gcd 函数的返回值会变大,则满足 ans 增加的条件数会变少。

T2
  1. 将第 14 行的“n=std::unique(a+1,a+n+1)-a-1;”删去后,有可能出现与原本代码不同的输出结果。(F
    阅读程序(2)解决的问题是:将 \(n\) 个数字分成尽可能少的组,要求每组中的极差小于 \(k\)
    unique 函数去重前后,对分组的情况无影响。

  2. 假设输入的 a 数组和 k 均为正整数,执行第 18 行代码时,一定满足的条件不包括?(B
    A j<=i B a[i]-a[j]>k C j<=n D a[j]<a[i]
    显然条件 A、C 一定满足,条件 D 满足的原因是:数组已经排序且去重,下标 j 总是小于 i 。
    条件 B 不一定满足的原因是:由 a[i] - a[j + 1] <= k 结合上述推理得 a[i] - a[j] >= a[i] - a[j + 1] 。但不能据此判断 a[i] - a[j] 与 k 的关系。

  3. 假设输入的 a 数组和 k 均为正整数,但a 数组不一定有序,则若误删去第 13 行的“std:: sort(a+1,a+n+1);”, 程序有可能出现的问题有?(B
    A 输出的答案比原本答案更大
    B 输出的答案比原本答案更小
    C 出现死循环行为
    D 以上均可能发生
    若数组无序,则 a[i] - a[j + 1] 值可能为负数,指针 j 不满足移动条件,ans[j] 较小,ans[i] 较小,ans[n] 较小。

T3
  1. 一个序列 b 与其升序排序后的序列 a 的最长公共子序列(LCS)为 b 的最长上升子序列(LIS)。
    (此题赛时因题号重复被删。)

程序填空

  1. 判断“挑战者与擂主”是否“意见不合”的语句?(D
    A query(candidate, i) == false
    B query(i, candidate) == true
    C query(candidate, i) == false && query(i, candidate) == false
    D query(candidate, i) == false || query(i, candidate) == false
    只要两人“意见不合”,就说明两人中至少有一人是糊涂人。所以是或者的关系。

S 组

单项选择

  1. 将字符串 "cat", "car", "cart", "case", "dog", "do" 插入一个空的 Trie 树(前缀树)中,构造完成 Trie 树(包括根节点)共有多少个结点?(D
    A 8 B 9 C 10 D 11
    1(root) + 3(cat) + 1(car) + 1(cart) + 2(case) + 3(dog) + 0(do) = 11 。

  2. 一个包含 8 个顶点的完全图(顶点的编号为 1 到 8 ),任意两点之间的边权重等于两顶点编号的差的绝对值。例如,顶点 3 和 7 之间的边权重为 |7 – 3| = 4。该图的最小生成树总权重是多少?(A
    A 7 B 8 C 9 D 10
    注意到,若该图的最小生成树退化成一条链(1 - 2 - 3 - 4 - 5 - 6 - 7 - 8)时,总权重最少为 8 - 1 = 7 。

  3. 1 到 1000 之间,不能被 2、3、5 中任意一个数整除的整数有多少个?(A
    A 266 B 267 C 333 D 734
    容斥原理。
    \(1000 - (\lfloor \frac{1000}{2} \rfloor+ \lfloor \frac{1000}{3} \rfloor + \lfloor \frac{1000}{5} \rfloor - \lfloor \frac{1000}{6} \rfloor - \lfloor \frac{1000}{15} \rfloor - \lfloor \frac{1000}{10} \rfloor + \lfloor \frac{1000}{30} \rfloor) = 266\)

阅读程序

T1
  1. 当输入的 n = 3 的时候,程序输出的答案为 3 。()

  2. 当输入的 n=4 的时候,程序输出的答案为?()
    A 11 B 12 C 24 D 9

  3. 如果因为某些问题,导致程序运行第 25 行的 dfs 函数之前,数组 p 的初值并不全为 0,则对程序的影响是?()
    A 输出的答案比原答案要小
    B 无法确定输出的答案
    C 程序可能陷入死循环
    D 没有影响

T2
  1. 不管输入的 n 和 k 具体为多少,t = 2 时的猜测数总是小于等于 t = 1 时的猜测数。()

  2. 函数 guess1 在运行过程中,cnt_broken 的值最多为?()
    A 0 B 1 C 2 D n

  3. 当输入的 n = 100 的时候,代码中 t = 1 和 t = 2 分别需要的猜测次数最多分别为?()
    A 100, 14 B 100, 13 C 99, 14 D 99, 13

T3
  1. 删除第 51 行的 “std::sort(ans2.begin(), ans2.end());” 后,代码输出的结果不会受到影响。()

程序填空

posted @ 2025-09-21 12:00  zheyutao  阅读(38)  评论(0)    收藏  举报