第一题(空间)

解题思路
答案
cpp
#include <stdio.h>
int main() {
// 计算256MB对应的字节数,1MB = 1024KB,1KB = 1024B
long long total_bytes = 256 * 1024 * 1024;
// 每个32位二进制整数占4个字节(32 / 8 = 4)
int bytes_per_int = 4;
// 计算可存储的整数个数
long long num = total_bytes / bytes_per_int;
printf("%lld\n", num);
return 0;
}
第二题(卡片)

解题思路
- 思路
- 采用模拟的方式,从1开始不断尝试拼出整数,每拼出一个整数,就减少该整数中各个数字对应的卡片数量。当某个数字的卡片数量不足时,就停止模拟,此时上一个成功拼出的整数就是答案。
- 具体过程
- 用一个长度为10的数组
card_count
来记录\(0 - 9\)每个数字卡片的数量,初始时每个元素的值都为2021。- 从1开始循环,对于每个整数i,将其转换为字符串(方便获取每一位数字),然后遍历该字符串,每遇到一个数字j,就将
card_count[j]
减1。如果在遍历过程中发现card_count[j]
小于0,说明该数字的卡片数量不足,此时循环结束,输出上一个成功拼出的整数。
答案
php
card_count = [2021] * 10
num = 1
while True:
num_str = str(num)
for digit in num_str:
digit = int(digit)
card_count[digit] -= 1
if card_count[digit] < 0:
print(num - 1)
exit()
num += 1
cpp
#include <stdio.h>
int main() {
int card_count[10];
for (int i = 0; i < 10; i++) {
card_count[i] = 2021;
}
int num = 1;
while (1) {
int temp = num;
while (temp > 0) {
int digit = temp % 10;
card_count[digit]--;
if (card_count[digit] < 0) {
printf("%d\n", num - 1);
return 0;
}
temp /= 10;
}
num++;
}
return 0;
}
第三题(直线)

解题思路
答案
python
from math import gcd
def simplify_fraction(a, b):
d = gcd(a, b)
return a // d, b // d
points = [(x, y) for x in range(20) for y in range(21)]
lines = set()
n = len(points)
for i in range(n):
for j in range(i + 1, n):
x1, y1 = points[i]
x2, y2 = points[j]
if x1 == x2:
lines.add((x1,))
else:
kx = y2 - y1
ky = x2 - x1
kx, ky = simplify_fraction(kx, ky)
b = y1 * ky - kx * x1
b, ky = simplify_fraction(b, ky)
lines.add((kx, ky, b))
print(len(lines))
cpp
#include <stdio.h>
#include <stdlib.h>
// 求最大公约数
int gcd(int a, int b) {
return b == 0? a : gcd(b, a % b);
}
// 简化分数形式的斜率和截距
void simplify(int *a, int *b) {
int d = gcd(*a, *b);
*a /= d;
*b /= d;
}
typedef struct {
int type; // 0表示非垂直直线,1表示垂直直线
union {
struct {
int kx;
int ky;
int b;
} non_vertical;
int x;
} data;
} Line;
// 比较直线是否相同
int compare_lines(const void *a, const void *b) {
const Line *l1 = (const Line *)a;
const Line *l2 = (const Line *)b;
if (l1->type != l2->type) {
return l1->type - l2->type;
}
if (l1->type == 0) {
if (l1->data.non_vertical.kx != l2->data.non_vertical.kx) {
return l1->data.non_vertical.kx - l2->data.non_vertical.kx;
}
if (l1->data.non_vertical.ky != l2->data.non_vertical.ky) {
return l1->data.non_vertical.ky - l2->data.non_vertical.ky;
}
return l1->data.non_vertical.b - l2->data.non_vertical.b;
}
return l1->data.x - l2->data.x;
}
int main() {
Line *lines = (Line *)malloc(20 * 21 * (20 * 21 - 1) / 2 * sizeof(Line));
int line_count = 0;
for (int x1 = 0; x1 < 20; x1++) {
for (int y1 = 0; y1 < 21; y1++) {
for (int x2 = x1; x2 < 20; x2++) {
for (int y2 = 0; y2 < 21; y2++) {
if (x1 != x2 || y1 != y2) {
if (x1 == x2) {
lines[line_count].type = 1;
lines[line_count].data.x = x1;
} else {
lines[line_count].type = 0;
lines[line_count].data.non_vertical.kx = y2 - y1;
lines[line_count].data.non_vertical.ky = x2 - x1;
simplify(&lines[line_count].data.non_vertical.kx, &lines[line_count].data.non_vertical.ky);
lines[line_count].data.non_vertical.b = y1 * lines[line_count].data.non_vertical.ky - lines[line_count].data.non_vertical.kx * x1;
simplify(&lines[line_count].data.non_vertical.b, &lines[line_count].data.non_vertical.ky);
}
line_count++;
}
}
}
}
}
qsort(lines, line_count, sizeof(Line), compare_lines);
int distinct_count = 0;
for (int i = 0; i < line_count; i++) {
if (i == 0 || compare_lines(&lines[i - 1], &lines[i]) != 0) {
distinct_count++;
}
}
printf("%d\n", distinct_count);
free(lines);
return 0;
}
第四题(货物摆放)

解析:这题的理解并不难,就是用三重循环暴力找出方案的总类,但是如果直接暴力,因为n十分大,所以直接暴力空间复杂度和时间复杂度是不太能行的,所以换种方法,先找出n的所有因数,再枚举因数的组合:
- 找出 的所有因数 :我们使用一个循环从 到 遍历,对于每个能整除 的数 ,将 和 都添加到因数列表
factors
中。这样可以避免重复计算因数。 - 枚举因数组合 :使用三重循环遍历因数列表,对于每一组因数 ,检查它们的乘积是否等于 。如果等于 ,则说明找到了一种有效的堆放方案,计数器
count
加 。
题解
cpp
#include <stdio.h>
#define N 2021041820210418LL
// 计算 n 的因数,并存储在 factors 数组中,返回因数的数量
int find_factors(long long n, long long factors[]) {
int count = 0;
for (long long i = 1; i * i <= n; i++) {
if (n % i == 0) {
factors[count++] = i;
if (i != n / i) {
factors[count++] = n / i;
}
}
}
return count;
}
int main() {
long long factors[100000]; // 假设因数数量不超过 100000
int factor_count = find_factors(N, factors);
int count = 0;
// 枚举因数组合
for (int i = 0; i < factor_count; i++) {
for (int j = 0; j < factor_count; j++) {
for (int k = 0; k < factor_count; k++) {
if (factors[i] * factors[j] * factors[k] == N) {
count++;
}
}
}
}
printf("%d\n", count);
return 0;
}
结果

第五题(路径)
题目:

解析:要求1到2021之间的最短路径,这是典型的最短路径问题,可以以采用Dijkstra算法来计算两点之间的最短路径问题。
Dijkstra算法是一种用于计算带权有向图或无向图中单个源节点到所有节点的最短路径的贪心算法。
- 构建图 :根据题目条件,对于两个不同的结点
a
和b
,如果|a - b| <= 21
,则在它们之间添加一条长度为a
和b
的最小公倍数的无向边。 - 实现最小公倍数计算:编写一个函数来计算两个数的最小公倍数。
- 使用 Dijkstra 算法:计算从结点 1 到结点 2021 的最短路径长度。
题解:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 2022
#define INF 0x3f3f3f3f
// 计算两个数的最大公约数
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 计算两个数的最小公倍数
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
// Dijkstra 算法
int dijkstra(int** graph, int start, int end) {
int dist[MAXN]; // 存储起点到各点的最短距离
int visited[MAXN]; // 标记节点是否已访问
memset(dist, INF, sizeof(dist));
memset(visited, 0, sizeof(visited));
dist[start] = 0;
for (int i = 1; i < MAXN; i++) {
int min_dist = INF, u = -1;
// 找到未访问节点中距离最小的节点
for (int j = 1; j < MAXN; j++) {
if (!visited[j] && dist[j] < min_dist) {
min_dist = dist[j];
u = j;
}
}
if (u == -1) break;
visited[u] = 1;
// 更新与 u 相邻节点的距离
for (int v = 1; v < MAXN; v++) {
if (!visited[v] && graph[u][v] != INF && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
return dist[end];
}
int main() {
int** graph = (int**)malloc(MAXN * sizeof(int*));
for (int i = 0; i < MAXN; i++) {
graph[i] = (int*)malloc(MAXN * sizeof(int));
}
//如果程序需要处理大量的数据,可能会导致栈溢出。因为 graph 数组是在栈上分配的,栈空间是有限的。当数组过大时,可能会超出栈的容量。解决办法:
//可以将 graph 数组改为在堆上分配内存,使用 malloc 函数
// 初始化图
for (int i = 1; i < MAXN; i++) {
for (int j = 1; j < MAXN; j++) {
if (i == j) {
graph[i][j] = 0;
}
else if (abs(i - j) <= 21) {
graph[i][j] = lcm(i, j);
}
else {
graph[i][j] = INF;
}
}
}
// 计算最短路径
int shortest_path = dijkstra(graph, 1, 2021);
printf("%d\n", shortest_path);
// 释放内存
for (int i = 0; i < MAXN; i++) {
free(graph[i]);
}
free(graph);
return 0;
}
gcd
函数:用于计算两个数的最大公约数,采用递归的方式实现。lcm
函数 :用于计算两个数的最小公倍数,通过公式lcm(a, b) = a / gcd(a, b) * b
计算。dijkstra
函数 :实现了 Dijkstra 算法,计算从起点start
到终点end
的最短路径长度。dist
数组用于存储起点到各点的最短距离,初始值为无穷大。visited
数组用于标记节点是否已访问,初始值为 0。- 每次选择未访问节点中距离最小的节点
u
,标记为已访问,并更新与u
相邻节点的距离。
结果

第六题(时间显示)

解题思路
- 输入处理 :借助
scanf
函数读取输入的毫秒数。- 单位转换:把毫秒数转换为秒数,因为不需要处理毫秒,所以直接除以 1000。
- 时间计算 :
- 用秒数除以 3600(一小时的秒数),再对 24 取模,得到小时数。
- 秒数对 3600 取模后再除以 60,得到分钟数。
- 秒数对 60 取模,得到秒数。
- 输出格式 :利用
%02d
格式化输出,保证时、分、秒不足两位时会补前导 0。
答案
cpp
#include <stdio.h>
int main() {
long long milliseconds;
scanf("%lld", &milliseconds);
// 将毫秒数转换为秒数
long long seconds = milliseconds / 1000;
// 计算小时、分钟和秒
int hours = seconds / 3600 % 24;
int minutes = seconds % 3600 / 60;
int secs = seconds % 60;
// 输出结果,不足两位时补前导0
printf("%02d:%02d:%02d", hours, minutes, secs);
return 0;
}
第七题(砝码称重)

解题思路
问题分析:
- 已知有 N 个砝码,重量分别为 \(W_1, W_2, \cdots, W_N\),砝码可以放在天平两边,目标是计算能称出多少种不同的正整数重量。
- 对于每个砝码,都有三种处理方式:不使用、放在天平左边、放在天平右边。
动态规划状态定义:
- 定义一个布尔型数组
dp
,dp[i]
表示是否能够称出重量为 i 的物品。这里使用一维数组就可以,因为我们只关心最终能否称出某个重量,而不需要记录是用哪些砝码称出的(如果需要记录具体砝码组合,可能需要更复杂的数据结构)。- 初始状态:
dp[0] = true
,表示不使用任何砝码时,可以称出重量为 0 的物品(这是一个特殊情况,为后续计算做准备)。动态规划状态转移:
- 对于每个砝码 \(W_i\),遍历当前已经能称出的所有重量 j(即
dp[j] == true
的 j)。- 当考虑加入砝码 \(W_i\) 时:
- 情况一:保持当前重量 j 不变(即不使用该砝码),这种情况不需要额外操作,因为
dp[j]
已经为true
。- 情况二:将砝码 \(W_i\) 放在天平左边,此时能称出的新重量为 \(j + W_i\),更新
dp[j + W_i] = true
(前提是 \(j + W_i < MAX_WEIGHT\),避免数组越界)。- 情况三:将砝码 \(W_i\) 放在天平右边,此时能称出的重量为 \(|j - W_i|\)(取绝对值)。具体计算时,若 \(j \geq W_i\),则新重量为 \(j - W_i\);若 \(j < W_i\),则新重量为 \(W_i - j\),更新
dp[|j - W_i|] = true
。- 为了避免重复更新,我们可以使用一个临时数组
temp
来记录当前砝码加入后能称出的重量,最后再将temp
数组的值复制回dp
数组。统计结果:
- 遍历
dp
数组,从 1 到MAX_WEIGHT - 1
,统计dp[j] == true
的个数,这个个数就是能称出的不同正整数重量的数量。内存管理:
- 由于使用了动态分配的数组(如
dp
数组),在程序结束前需要使用free
函数释放这些数组占用的内存,以防止内存泄漏。
答案
cpp
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MAX_WEIGHT 100001
int main() {
int n;
int weights[101];
bool *dp = (bool *)calloc(MAX_WEIGHT, sizeof(bool));
// 读取砝码数量
scanf("%d", &n);
// 读取每个砝码的重量
for (int i = 0; i < n; i++) {
scanf("%d", &weights[i]);
}
// 初始化 dp 数组
dp[0] = true;
// 动态规划过程
for (int i = 0; i < n; i++) {
// 临时数组用于记录当前砝码加入后能称出的重量
bool *temp = (bool *)calloc(MAX_WEIGHT, sizeof(bool));
for (int j = 0; j < MAX_WEIGHT; j++) {
if (dp[j]) {
temp[j] = true;
// 加上当前砝码
if (j + weights[i] < MAX_WEIGHT) {
temp[j + weights[i]] = true;
}
// 减去当前砝码(取绝对值)
int diff = j > weights[i] ? j - weights[i] : weights[i] - j;
temp[diff] = true;
}
}
// 将临时数组的值复制回 dp 数组
for (int j = 0; j < MAX_WEIGHT; j++) {
dp[j] = temp[j];
}
// 释放临时数组内存
free(temp);
}
// 统计可以称出的不同正整数重量的数量
int count = 0;
for (int j = 1; j < MAX_WEIGHT; j++) {
if (dp[j]) {
count++;
}
}
// 输出结果
printf("%d\n", count);
// 释放动态分配的内存
free(dp);
return 0;
}
第八题(杨辉三角)

解题思路
理解杨辉三角与目标数列
杨辉三角具有如下特点:
第 n 行(从 0 开始计数 )有 \(n + 1\) 个元素。
第 n 行第 k 个元素(k 从 0 开始计数 )的值等于组合数 \(C_{n}^{k}=\frac{n!}{k!(n - k)!}\) 。 将杨辉三角按从上到下、从左到右顺序排成数列,我们要在这个数列中找 N 首次出现的位置。
遍历查找策略
逐行遍历:从杨辉三角的第 0 行开始,依次处理每一行。因为杨辉三角前面的行元素个数少,先处理前面行可以更快定位到较小的数,符合找首次出现位置的要求。
计算每行元素:对于当前处理的第 n 行,通过组合数公式计算该行第 k 个元素的值。比如计算第 n 行第 k 个元素时,就用 \(C_{n}^{k}\) 的公式,在代码中通过循环来实现这个公式的计算逻辑。
记录位置 :使用一个变量(如代码中的
index
)来记录遍历过的元素个数。每处理一个元素,index
就加 1 。当找到等于 N 的元素时,此时index
的值就是 N 在数列中首次出现的位置,输出该值并结束查找过程。终止条件
当找到与 N 相等的元素时,就找到了 N 在数列中首次出现的位置,程序可以终止。如果遍历完很多行(实际做题时要合理设定一个较大的行数范围 )都没找到 N ,也可按题目要求处理(本题默认能找到 )
答案
cpp
#include <stdio.h>
// 计算组合数函数
int combination(int n, int k) {
int result = 1;
for (int i = 1; i <= k; i++) {
result = result * (n - (i - 1)) / i;
}
return result;
}
int main() {
int n;
scanf_s("%d", &n);
int index = 1;
for (int i = 0; i < 1000; i++) { // 这里假设足够多行能找到目标数,可按需调整范围
for (int j = 0; j <= i; j++) {
int num = combination(i, j);
if (num == n) {
printf("%d\n", index);
return 0;
}
index++;
}
}
return 0;
}
第九题(双向排序)

解题思路
问题理解:
输入包括数组的长度
n
和操作的次数m
。每次操作由两个整数
x
和y
表示,x
为操作类型(0
表示一种操作,1
表示另一种操作),y
是与操作相关的参数。目标是根据这些操作对一个初始为
1
到n
的数组进行调整,得到最终的数组顺序。数据结构选择:
使用一个结构体数组
s
来模拟栈,结构体PII
包含两个成员first
和second
,分别存储操作类型和对应的参数。用一个数组
ans
来存储最终的结果数组。操作处理逻辑:
0
操作处理:
当遇到
0
操作时,如果栈顶元素也是0
操作(即连续的0
操作),则取当前y
和栈顶y
的较大值,并弹出栈顶元素。这是因为连续的0
操作中,只保留范围较大的那个即可。然后检查栈中是否存在可以被当前
0
操作覆盖的0
操作(即栈中前一个0
操作的参数小于等于当前y
),如果存在则弹出这两个元素(因为它们被覆盖了,不需要保留)。最后将当前
0
操作及其参数压入栈中。
1
操作处理:
当遇到
1
操作且栈不为空时,如果栈顶元素也是1
操作,则取当前y
和栈顶y
的较小值,并弹出栈顶元素。接着检查栈中是否存在可以被当前
1
操作覆盖的1
操作(即栈中前一个1
操作的参数大于等于当前y
),如果存在则弹出这两个元素。最后将当前
1
操作及其参数压入栈中。生成结果数组:
操作处理完成后,开始根据栈中的操作记录来填充结果数组
ans
。从
1
到top
遍历栈,对于每个操作:
如果是
0
操作,从r
(初始为n
)开始,当r
大于当前y
且l
(初始为1
)小于等于r
时,将k
(初始为n
)赋值给ans[r]
,然后r--
和k--
。这是因为0
操作表示降序处理一部分数组,所以从后往前填充较大的数字。如果是
1
操作,从l
开始,当l
小于当前y
且l
小于等于r
时,将k
赋值给ans[l]
,然后l++
和k--
。这是因为1
操作表示升序处理一部分数组,所以从前往后填充较小的数字。如果遍历完栈后
l
大于r
,说明已经填充完所有数字,结束填充过程。处理剩余数字:
如果栈的元素个数
top
为奇数,说明最后一个操作是0
操作(降序操作),则从l
开始将剩余的数字依次填充到ans
数组中。如果
top
为偶数,说明最后一个操作是1
操作(升序操作),则从r
开始将剩余的数字依次填充到ans
数组中。输出结果:
- 最后,按顺序输出数组
ans
的所有元素,得到最终的结果。
答案
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 定义一个结构体来模拟 pair 类型
typedef struct {
int first;
int second;
} PII;
PII s[100005];
int n, m;
int top;
int ans[100005];
int main() {
scanf_s("%d %d", &n, &m);
int x, y;
for (int i = 1; i <= m; ++i) {
scanf_s("%d%d", &x, &y);
if (!x) { // 0 操作
if (top && s[top - 1].first == 0) { // 对于连续的 0 操作只保留一个
y = (y > s[top - 1].second) ? y : s[top - 1].second;
top--;
}
while (top >= 2 && s[top - 2].second <= y) { // 0 操作覆盖掉乱序区
top -= 2;
}
s[top].first = 0;
s[top].second = y;
top++; // 插入
}
else if (top) { // 栈不为空,且为 1 操作。
if (top && s[top - 1].first == 1) {
y = (y < s[top - 1].second) ? y : s[top - 1].second;
top--;
}
while (top >= 2 && s[top - 2].second >= y) {
top -= 2;
}
s[top].first = 1;
s[top].second = y;
top++;
}
}
int k = n, l = 1, r = n;
for (int i = 0; i < top; ++i) { // 将固定的数字插入 ans 数组里
if (s[i].first == 0) {
while (r > s[i].second && l <= r) ans[r--] = k--;
}
else {
while (l < s[i].second && l <= r) ans[l++] = k--;
}
if (l > r) break;
}
// 如果有数字没被固定,就还需要将它们存入 ans 数组里
if (top % 2 != 0) {
while (k > 0) ans[l++] = k--;
}
else {
while (k > 0) ans[r--] = k--;
}
for (int i = 1; i <= n; ++i) {
printf("%d ", ans[i]);
}
return 0;
}
第十题(括号序列)

解题思路
整个问题的解决分为两个主要步骤,分别对原始括号序列和经过反转并交换左右括号后的序列进行处理,计算各自的方案数,最后将这两个方案数相乘并对
mod
取模得到最终结果。具体步骤分析
- 读取输入
在
main
函数中,首先读取用户输入的括号序列字符串s
,并获取其长度n
。这是整个问题的基础数据,后续的操作都基于这个括号序列展开。
- 定义状态和初始化
状态定义 :使用二维数组
f[i][j]
来表示状态,其中i
表示处理到括号序列的第i
个字符,j
表示当前左括号比右括号多的数量。初始化 :在
get
函数中,将f[0][0]
初始化为1
。这表示在处理括号序列之前(即第 0 个字符),左括号和右括号数量相等的方案数为 1,是一个合法的起始状态。
- 状态转移
在
get
函数里,通过两层循环遍历括号序列进行状态转移:
遇到左括号
(
时:
- 当处理到第
i
个字符且该字符为左括号时,左括号比右括号多的数量会增加 1。所以对于j
从 1 到n
的情况,f[i][j]
的值等于f[i - 1][j - 1]
。这意味着当前状态下左括号比右括号多j
个的方案数,是由前一个状态下左括号比右括号多j - 1
个的方案数转移过来的。遇到右括号
)
时:
对于
f[i][0]
,它的值等于(f[i - 1][1] + f[i - 1][0]) % mod
。这是因为当左括号和右括号数量相等时(j = 0
),可以从之前左括号比右括号多 1 个的状态转移过来,也可以保持原来左括号和右括号数量相等的状态。对于
j
从 1 到n
的情况,f[i][j]
的值等于(f[i - 1][j + 1] + f[i][j - 1]) % mod
。这表示当前状态下左括号比右括号多j
个的方案数,既可以从之前左括号比右括号多j + 1
个的状态转移过来(因为添加了一个右括号使得差值减少),也可以从当前状态下左括号比右括号多j - 1
个的状态转移过来(考虑之前状态的延续)。
- 寻找最终方案数
在完成状态转移后,在
get
函数中通过遍历f[n][i]
(i
从 0 到n
),找到第一个不为 0 的值并返回。这个值就是处理完整个括号序列后,满足某种条件(可能是特定的合法括号组合)的方案数。如果都为 0,则返回 -1。
- 反转并交换括号
在
main
函数中,调用reverse_and_swap
函数对原始括号序列进行处理。该函数会将字符串反转,同时把左括号和右括号进行交换。这样做的目的是得到一个新的括号序列,然后对这个新序列再次进行上述的状态转移和方案数计算。
- 计算最终结果
将对原始括号序列计算得到的方案数
x
和对反转并交换括号后的序列计算得到的方案数y
相乘,然后对mod
取模,得到最终的结果并输出。
答案
cpp
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using LL=long long;
const int N = 5005;
int f[N][N];
int mod=1e9+7;
string s;
int n;
LL get(){
memset(f,0,sizeof f);
f[0][0]=1;
for(int i=1;i<=n;i++){
if(s[i-1]=='('){
for(int j=1;j<=n;j++)
f[i][j]=f[i-1][j-1];
}
else{
f[i][0]=(f[i-1][1]+f[i-1][0])%mod;
for(int j=1;j<=n;j++)
f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
}
}
for(int i=0;i<=n;i++)
if(f[n][i])
return f[n][i];
return -1;
}
int main(){
cin>>s;
n=s.size();
LL x=get();
reverse(s.begin(),s.end());
for(int i=0;i<n;i++){
if(s[i]==')')
s[i]='(';
else
s[i]=')';
}
LL y=get();
cout<<(x*y)%mod;
}