河南工大2026新生程序设计周赛第(一)场题解

本文题解由 AI 辅助整理,参考程序已进行本地测试,欢迎指正。

比赛链接 | 题面与参考程序

本场题解均不需要使用数组。每题先给出 C 语言参考代码,再附 C++ 版本,任选一种阅读即可。C 代码使用 C99 或更新标准,C++ 代码使用 C++11 或更新标准。

A · 寻室

题目链接

按编号从小到大检查每间教室。第 \(i\) 间教室可选,需要同时满足三个条件:

  • \(a_i\ge p\):座位足够;
  • \(b_i\ge q\):插座足够;
  • \(c_i=0\):教室空闲。

对于可选教室,计算距离 \(|i-s|\)。用 ans 记录目前选中的编号,minDist 记录对应距离;遇到距离更小的教室就更新。

注意,更新条件是 d < minDist,不能写成 <=。因为按照编号递增的顺序检查,距离相等时,先遇到的教室编号更小,保留原答案即可。

初始化 ans = -1,若没有可选教室,最后自然输出 -1。minDist 初始化为 n,比任意两间教室的距离都大。当前位置也可以选择,距离为 0。

每间教室读入后立即判断,不用保存所有教室。时间复杂度 \(O(n)\),额外空间复杂度 \(O(1)\)。

C 中 abs() 用于求整数绝对值,需要包含 。最后输出的是教室编号,不是距离。

C 语言参考代码

#include 
#include int main(void)
{int n, s, p, q;scanf("%d%d%d%d", &n, &s, &p, &q);int ans = -1;int minDist = n;  /* 任意两间教室的距离最多为 n - 1 */for (int i = 1; i <= n; i++){int a, b, c;scanf("%d%d%d", &a, &b, &c);if (a >= p && b >= q && c == 0){int d = abs(i - s);if (d < minDist){minDist = d;ans = i;}}}printf("%d\n", ans);return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{int n, s, p, q;cin >> n >> s >> p >> q;int ans = -1;int minDist = n;  // 任意两间教室的距离最多为 n - 1for (int i = 1; i <= n; i++){int a, b, c;cin >> a >> b >> c;if (a >= p && b >= q && c == 0){int d = abs(i - s);if (d < minDist){minDist = d;ans = i;}}}cout << ans << '\n';return 0;
}

B · 雨山

题目链接

设从左往右的累计上升量为 \(U\),累计下降量为 \(D\)。

总起伏量是两者之和,终点与起点的高度差是两者之差,因此:

\[U+D=s,\qquad U-D=b-a. \]

反向走时,原来的下降就变成了上升,所以两个方向的累计上升量分别是 \(U\) 和 \(D\)。选择较大的一个,有:

\[\max(U,D)=\frac{s+|a-b|}{2}. \]

每上升一单位,最多消耗一单位水汽并产生一单位雨水;水汽用完后不能继续降雨。因此答案为:

\[\min\left(w,\frac{s+|a-b|}{2}\right). \]

不需要知道具体山形。注意下降时不会补充水汽,高度差要取绝对值。代码使用 long long,C 中对应的读写格式是 %lld。

时间复杂度 \(O(1)\),额外空间复杂度 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{long long a, b, s, w;scanf("%lld%lld%lld%lld", &a, &b, &s, &w);long long diff = a >= b ? a - b : b - a;long long rise = (s + diff) / 2;long long ans = rise < w ? rise : w;printf("%lld\n", ans);return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{long long a, b, s, w;cin >> a >> b >> s >> w;long long diff = a >= b ? a - b : b - a;long long rise = (s + diff) / 2;long long ans = rise < w ? rise : w;cout << ans << '\n';return 0;
}

C · 藤萱

题目链接

设数量较少的颜色有 \(m=\min(r,y)\) 朵。

每一对异色相邻花朵都包含一朵数量较少的颜色的花,而每朵这样的花只有两个邻居,因此美丽值最多为 \(2m\)。

这个上限可以达到:让数量较少的颜色的每朵花两侧都放另一种颜色,多出来的同色花放在一起即可。例如 5 朵红花、3 朵黄花,可以排成:

红 黄 红 黄 红 黄 红 红(首尾相接)

每朵黄花的两侧都是红花,共有 6 对异色相邻花朵。因此答案就是:

\[2\min(r,y). \]

注意这是花环,第一朵和最后一朵也相邻。如果其中一种颜色的数量为 0,答案为 0。

时间复杂度 \(O(1)\),额外空间复杂度 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{long long r, y;scanf("%lld%lld", &r, &y);long long ans = 2 * (r < y ? r : y);printf("%lld\n", ans);return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{long long r, y;cin >> r >> y;long long ans = 2 * (r < y ? r : y);cout << ans << '\n';return 0;
}

D · 故站

题目链接

本题没有输入,直接输出:

Hautoj will be back?

注意 Hautoj 只有首字母大写,单词之间是一个空格,末尾是半角问号。不要输出引号或其他提示文字。

C 语言参考代码

#include 
#include int main(void)
{puts("Hautoj will be back?");return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{cout << "Hautoj will be back?\n";return 0;
}

E · 回首

题目链接

恰好改变两次方向,意味着路线由三段非空的单向行走组成。

设第一次折返的位置为 \(x\),第二次折返的位置为 \(y\),整条路线就是:

\[s\to x\to y\to e. \]

枚举 \(0\le x,y\le L\),分别判断两种情况:

  • 先右、再左、最后右:\(x>s\)、\(y、\(e>y\);
  • 先左、再右、最后左:\(x、\(y>x\)、\(e。

这里必须使用严格不等号,否则会把长度为 0 的路段也算进去。

方向满足要求后,再检查总步数是否恰好为 \(T\):

\[|x-s|+|y-x|+|e-y|=T. \]

两个条件都满足,就将答案加一。

为什么不会漏算或重复?每条合法路线都有唯一的两个折返点;反过来,每一对通过检查的折返点,也唯一确定一条合法路线。三段的端点都在步道内,段内又只朝一个方向走,因此中途不会越界。

\(L\le500\),最多检查 \(501^2=251001\) 对位置,直接两层循环即可。不要按可能达到十亿的 \(T\) 逐分钟模拟。

时间复杂度 \(O(L^2)\),额外空间复杂度 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{int L, s, e, T;scanf("%d%d%d%d", &L, &s, &e, &T);int ans = 0;for (int x = 0; x <= L; x++){for (int y = 0; y <= L; y++){int rightFirst = x > s && y < x && e > y;int leftFirst = x < s && y > x && e < y;int steps = abs(x - s) + abs(y - x) + abs(e - y);if ((rightFirst || leftFirst) && steps == T)ans++;}}printf("%d\n", ans);return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{int L, s, e, T;cin >> L >> s >> e >> T;int ans = 0;for (int x = 0; x <= L; x++){for (int y = 0; y <= L; y++){int rightFirst = x > s && y < x && e > y;int leftFirst = x < s && y > x && e < y;int steps = abs(x - s) + abs(y - x) + abs(e - y);if ((rightFirst || leftFirst) && steps == T)ans++;}}cout << ans << '\n';return 0;
}

F · 蜂巢

题目链接

先输出第一条水平边 ...+---+。之后每添加一个蜂巢,只追加下面四行:

../.....\
.+.......+
..\...../
...+---+

刚输出的下边,可以直接作为下一个蜂巢的上边,所以共用边不需要重复输出。循环 \(n\) 次,一共输出 \(4n+1\) 行。

注意:点号必须输出,不能用空格代替。字符串中的一个反斜杠 \ 要写成 \\,正斜杠 / 不需要转义。

时间复杂度 \(O(n)\),额外空间复杂度 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{int n;scanf("%d", &n);puts("...+---+");for (int i = 0; i < n; i++){puts("../.....\\");puts(".+.......+");puts("..\\...../");puts("...+---+");}return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{int n;cin >> n;cout << "...+---+\n";for (int i = 0; i < n; i++){cout << "../.....\\\n";cout << ".+.......+\n";cout << "..\\...../\n";cout << "...+---+\n";}return 0;
}

G · 红雨

题目链接

\(n\le100000\),整场雨最多持续 199999 分钟,可以直接逐分钟累加。

第 \(t\) 分钟的雨滴数为:

\[\begin{cases} t, & t\le n,\\ 2n-t, & t>n. \end{cases} \]

每加完一分钟,检查累计雨滴数是否大于或等于 \(k\)。第一次满足条件时,当前分钟就是最早答案;全部分钟结束仍不满足,就输出 -1。

峰值 \(n\) 只出现一次。累计雨量最大为 \(n^2=10^{10}\),而 \(k\) 最大为 \(10^{12}\),所以累计值和 \(k\) 都要使用 long long,不能用 int。

时间复杂度 \(O(n)\),额外空间复杂度 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{int n;long long k;scanf("%d%lld", &n, &k);long long sum = 0;int ans = -1;for (int t = 1; t <= 2 * n - 1; t++){int rain = t <= n ? t : 2 * n - t;sum += rain;if (sum >= k){ans = t;break;}}printf("%d\n", ans);return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{int n;long long k;cin >> n >> k;long long sum = 0;int ans = -1;for (int t = 1; t <= 2 * n - 1; t++){int rain = t <= n ? t : 2 * n - t;sum += rain;if (sum >= k){ans = t;break;}}cout << ans << '\n';return 0;
}

H · 一步之遥

题目链接

这题的关键是:从左到右,每次选能够填完后续位置的最小数字。

位数相同的两个正整数,第一处不同的数位越小,整个数就越小。但不能只顾当前数字小,还要判断后面能否补足要求的数位和。

如何判断当前数字能不能选?

用 remaining 表示还没有填写的数位需要凑出的和,用 previous 保存前一位。

第一位从 1 到 9 尝试;其他位置从 0 到 9 尝试,并检查 abs(d - previous) <= 1。

假设当前选择 \(d\),后面还有 \(m\) 位。为了让后面的和最小,每一步减 1,降到 0 后保持为 0;为了让和最大,每一步加 1,升到 9 后保持为 9。

例如当前数字为 3,后面还有 4 位:

最小后缀:2100,数位和为 3
最大后缀:4567,数位和为 22

后面第 \(j\) 位的最小值为 \(\max(0,d-j)\),最大值为 \(\min(9,d+j)\)。分别累加得到 low 和 high。这两个极端后缀本身也合法,因此上下界都能达到。

如果后缀的可达数位和在上下界之间没有缺口,那么当前数字可选,当且仅当:

\[\text{low}\le\text{remaining}-d\le\text{high}. \]

满足时,立即输出当前数字,更新剩余和及前一位,再继续填写。候选数字确定之前,不修改 remaining,这样不需要试探失败后再恢复。

为什么上下界之间没有缺口?

这是区间判断成立的关键,不能只证明最大值、最小值能达到。

固定当前数字 \(d\),后面第 \(j\) 位的上界记为 \(u_j=\min(9,d+j)\)。从最小合法后缀开始,重复下面的操作:

在还没达到各自上界的位置中,选当前数字最小的一位,将它加 1。

这个操作不会破坏条件:

  • 这一位还没到上界,加 1 后不会越过上界或超过 9。
  • 加 1 只可能与一个比它小 1 的邻位冲突。但相邻位置的上界最多相差 1,所以这个更小的后缀邻位也一定没到自己的上界,与我们已经选了最小数字矛盾。
  • 固定的前一位 \(d\) 也不会阻止增加:如果后缀第一位已是 \(d+1\),它已经达到自己的上界,不会再被选中。

因此,可以一直合法地增加到最大后缀,每次总和只增加 1,最小和与最大和之间的每一个整数就都能实现。后面没有位置时,可达和只有 0,同样成立。

无解判断与最小性

\(n\) 位正整数的数位和介于 1 和 \(9n\) 之间,而且这之间的每个和都能实现:从 100…0 出发,每次选尚未达到 9 的位置中数字最小的一位加 1。更小的邻位若存在,也尚未达到 9,与选择最小数字矛盾,所以相邻限制始终满足;首位也不会变成 0。最终能到达 999…9,中间每一步的和增加 1。

因此先判断 \(s>9n\),成立就输出 -1。程序也检查 \(s<1\),尽管题目已保证 \(s\ge1\)。

初始有解,每次又只选能够补全的数字,所以不会输出一半才发现无解。最后一位的 low、high 都为 0,保证剩余和恰好用完。

每次从小到大尝试,选到的是当前前缀下最小的可行数字。如果还有更小的合法答案,在首次不同的位置,它应当使用一个更小且可行的数字,那它本来就应该先被选中,矛盾。因此得到的就是最小答案。

共有 \(n\) 个位置,每位最多尝试 10 个数字,每次用循环计算后缀上下界,时间复杂度 \(O(10n^2)\),即 \(O(n^2)\)。\(n\le100\),足够使用。

确定一位就输出一位,不需要数组,也不用把完整答案存进整数,额外空间复杂度为 \(O(1)\)。

C 语言参考代码

#include 
#include int main(void)
{int n, s;scanf("%d%d", &n, &s);if (s < 1 || s > 9 * n){puts("-1");return 0;}int remaining = s;int previous = -1;for (int i = 1; i <= n; i++){for (int d = (i == 1 ? 1 : 0); d <= 9; d++){if (i > 1 && abs(d - previous) > 1)continue;int low = 0, high = 0;int smallest = d, largest = d;for (int k = i + 1; k <= n; k++){if (smallest > 0) smallest--;if (largest < 9) largest++;low += smallest;high += largest;}int rest = remaining - d;if (low <= rest && rest <= high){putchar('0' + d);remaining = rest;previous = d;break;}}}putchar('\n');return 0;
}

C++ 参考代码

#include 
#include 
using namespace std;int main()
{int n, s;cin >> n >> s;if (s < 1 || s > 9 * n){cout << "-1\n";return 0;}int remaining = s;int previous = -1;for (int i = 1; i <= n; i++){for (int d = (i == 1 ? 1 : 0); d <= 9; d++){if (i > 1 && abs(d - previous) > 1)continue;int low = 0, high = 0;int smallest = d, largest = d;for (int k = i + 1; k <= n; k++){if (smallest > 0) smallest--;if (largest < 9) largest++;low += smallest;high += largest;}int rest = remaining - d;if (low <= rest && rest <= high){cout << d;remaining = rest;previous = d;break;}}}cout << '\n';return 0;
}