E5 练习赛解题记录(原题面缺失)
这不是一份完整题单。源目录只保留了 E5 - Solution.pdf 和我当时 A~H 的答题代码,没有原始题面;解析册也多次直接引用“题目描述”,没有重印完整输入、输出与数据约束。
因此,下面折叠外只记录解析册能够直接确认的题名、难度、考点和任务轮廓,不补造缺失条件;折叠内整理解析册思路与我留存代码的核心实现。我没有保留 I、J 的代码,只记录解析册方案。若要重新提交这些题,必须先找到原 OJ 题面核对全部约束。
A. 无幂之幂
- 难度:1
- 考点:函数
- 可确认任务:解析册示例对多组整数 ,自行实现整数幂和绝对值,输出 ;代码中禁止出现解析册列出的
math.h、pow、abs字符串。完整取值范围未保留。
查看解析与我的核心代码
用循环连乘实现非负整数指数,用分支实现绝对值。解析册还用这题提醒:函数式宏是文本替换,每个参数和整个展开式都要加括号;即便括号正确,也不要传入 i++ 这类带副作用表达式。
我的核心实现:
int absolute_value(int x)
{
return x >= 0 ? x : -x;
}
int main()
{
int a, b;
while (scanf("%d%d", &a, &b) == 2) {
int power_ab = 1;
int power_ba = 1;
for (int i = 0; i < b; ++i) power_ab *= a;
for (int i = 0; i < a; ++i) power_ba *= b;
printf("%d\n", absolute_value(power_ab - power_ba));
}
return 0;
}
由于原范围缺失,无法确认 int 是否始终足够,也不能确认指数是否保证非负。
B. 阶阶乘乘
- 难度:2
- 考点:函数、模运算
- 可确认任务:解析册示例定义阶乘取模函数,并把第一次调用结果再次作为函数实参,即计算形如
factorial_mod(factorial_mod(n))的结果;模数为 1000000007。原题对 的约束和数学表达式未完整保留。
查看解析与我的核心代码
每乘一个因子就取模,避免先产生完整阶乘:
const long long MOD = 1000000007LL;
long long factorial_mod(long long n)
{
long long result = 1;
while (n > 0) {
result = result * n % MOD;
--n;
}
return result;
}
int main()
{
long long n;
while (scanf("%lld", &n) == 1) {
printf("%lld\n", factorial_mod(factorial_mod(n)));
}
return 0;
}
必须特别小心:一般而言,先把内层阶乘对模数取余,再拿余数当外层阶乘的“自变量”,并不天然等价于先算完整内层阶乘。解析册说明这里依赖原题范围与模数关系;由于约束已缺失,不能把这份写法推广到任意模数和任意 。
C. 朗伯 W 函数
- 难度:3
- 考点:二分法求函数零点
- 可确认任务:解析册示例在区间 内求满足 的数值解,输出 6 位小数; 的完整范围未保留。
查看解析与我的核心代码
把问题改写为在单调区间上寻找 f(w) = w * exp(w) 与输入 x 的交点。若 f(mid) > x,答案在左半边;否则在右半边。
#include <math.h>
#include <stdio.h>
double f(double w)
{
return w * exp(w);
}
int main()
{
double x;
scanf("%lf", &x);
double left = -1.0;
double right = 10.0;
while (right - left > 1e-8) {
double mid = (left + right) / 2.0;
if (f(mid) > x) right = mid;
else left = mid;
}
printf("%.6f", (left + right) / 2.0);
return 0;
}
二分正确性的前提是原题保证解存在于该区间,且所用分支上的函数单调;这两个条件应以原题为准。
D. 经典的三角形
- 难度:3
- 考点:函数
- 可确认任务:解析册读入 10 个边长,按固定的 7 组三元下标调用分类函数;先判断是否成三角形,再按最大角分类为锐角、直角或钝角,并判断等腰或等边。完整输入输出说明未保留。
查看解析与我的核心代码
先把最大边放到 a,即可用三角形不等式和勾股关系分类:
void triangle(int number, long long a, long long b, long long c)
{
printf("Question %d:\n", number);
if (b > a) { long long t = a; a = b; b = t; }
if (c > a) { long long t = a; a = c; c = t; }
if (b + c <= a) {
printf("no triangle\n");
return;
}
long long left = b * b + c * c;
long long right = a * a;
if (left > right) printf("acute triangle\n");
else if (left == right) printf("right triangle\n");
else printf("obtuse triangle\n");
if (a == b && b == c) printf("equilateral triangle\n");
else if (a == b || b == c || a == c) printf("isosceles triangle\n");
}
我留存的代码把“角分类”和“等腰分类”拆成 f1、f2 两个函数,再用二维下标表保存 7 组三元组合。上面合并为一段是为了阅读方便,输出字符串沿用解析册。
E. 计算不确定度
- 难度:3
- 考点:循环、浮点数、计算
- 可确认任务:读入一组测量数据,输出平均值与 A 类标准不确定度,均保留 6 位小数。原题的样例和 的范围未保留。
查看解析与我的核心代码
解析册使用:
必须先读完并得到平均值,才能计算各项偏差。
#include <math.h>
#include <stdio.h>
int main()
{
double data[1000];
double average = 0.0;
double sum_square = 0.0;
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%lf", &data[i]);
average += data[i];
}
average /= n;
for (int i = 0; i < n; ++i) {
double difference = data[i] - average;
sum_square += difference * difference;
}
double uncertainty = sqrt(sum_square / n / (n - 1));
printf("%.6f\n%.6f", average, uncertainty);
return 0;
}
公式要求 ;是否由原题保证,需要找回原题确认。
F. 哪吒的水题
- 难度:4
- 考点:二分法、三分法
- 可确认任务:在两个给定横坐标之间选择供水站位置,使到两个给定点的加权距离和最小,并输出位置与最小费用,各保留 3 位小数。输入六个参数的确切命名和范围未完整保留。
查看解析与我的核心代码
解析册给出两条路线:对费用函数求导后二分导数零点;或利用费用函数先减后增的单峰性质直接三分。我留存的是三分版本:
#include <math.h>
#include <stdio.h>
double cost(double x,
double x1, double y1,
double x2, double y2,
double weight1, double weight2)
{
double d1 = hypot(x - x1, y1);
double d2 = hypot(x - x2, y2);
return weight1 * d1 + weight2 * d2;
}
int main()
{
double x1, y1, x2, y2, weight1, weight2;
scanf("%lf%lf%lf%lf%lf%lf",
&x1, &y1, &x2, &y2, &weight1, &weight2);
double left = x1;
double right = x2;
while (right - left > 1e-8) {
double mid1 = (2 * left + right) / 3.0;
double mid2 = (left + 2 * right) / 3.0;
if (cost(mid1, x1, y1, x2, y2, weight1, weight2)
> cost(mid2, x1, y1, x2, y2, weight1, weight2)) {
left = mid1;
} else {
right = mid2;
}
}
double answer = (left + right) / 2.0;
printf("%.3f %.3f",
answer,
cost(answer, x1, y1, x2, y2, weight1, weight2));
return 0;
}
三分的必要前提是搜索区间内目标函数单峰。若端点次序、权值符号等条件不满足,区间和单峰性都要重新检查。
G. 格雷码 2023
- 难度:5
- 考点:递归、分治
- 可确认任务:输入位数 ,按解析册规定次序输出全部 位格雷码。原题的 上限未保留。
查看解析与我的核心代码
格雷码相邻两项只有一位不同。解析册给出三种方法:递归镜像、二维数组构造、位运算公式。
最短的是:第 个格雷码等于
#include <stdio.h>
void print_bits(unsigned value, int n)
{
for (int bit = n - 1; bit >= 0; --bit) {
printf("%u", (value >> bit) & 1u);
}
putchar('\n');
}
int main(void)
{
int n;
scanf("%d", &n);
for (unsigned x = 0; x < (1u << n); ++x) {
print_bits(x ^ (x >> 1), n);
}
return 0;
}
我留存的代码用数组按镜像关系逐层构造,属于解析册的第二种方案。位运算版本更短,但 1u << n 要求 小于 unsigned 位宽,具体上限仍需原题确认。
H. 让废土重获生机
- 难度:5
- 考点:递归思想
- 可确认任务:对一个 区域,分别输出能量石数、有生机区域数和无生机区域数;摆放规则和输入范围只在缺失原题中完整定义,解析册把二维计数拆成两个一维递归。
查看解析与我的核心思路
解析册定义一维函数 表示长度为 时可放置的数量:
long long count_one_dimension(long long n)
{
if (n <= 2) return 1;
if (n <= 4) return 2;
if (n % 2 == 1) {
return 2 * count_one_dimension((n + 1) / 2) - 1;
}
return count_one_dimension(n / 2)
+ count_one_dimension(n / 2 + 1) - 1;
}
然后令 rows = f(m)、columns = f(n):
long long stones = rows * columns;
long long dead = (m - rows) * (n - columns);
long long alive = m * n - dead;
我留存的代码也先分别处理 、,再组合三个计数,但把多个小规模情况展开成了分支。这里展示解析册更直接的递推式。
解析册特别提醒:同一个递归子问题不要重复调用,可先保存结果;否则看似只多一次调用,递归树却可能成倍扩大。
I. 哪吒的分形
- 难度:5
- 考点:递归、循环、二维数组
- 可确认任务:按给定阶数输出一个中心挖空、四周递归复制的方形分形。原始图样、空格格式和阶数上限应以缺失原题为准。
查看解析册的三种方案
解析册给出:
- 递归输出每一行;
- 对每个坐标递归判断输出
1还是空格; - 用二维数组复制四份,再把中心区域清空。
第二种最容易理解。设当前层边长的一半为 half,若坐标落在中央区域就输出空格,否则把坐标对 half 取模并进入下一层:
void print_cell(int row, int column, int level)
{
if (level == 0) {
putchar('1');
return;
}
int half = 1 << (level - 1);
int in_middle_rows = half <= row && row < 3 * half;
int in_middle_columns = half <= column && column < 3 * half;
if (in_middle_rows && in_middle_columns) {
putchar(' ');
} else {
print_cell(row % (2 * half), column % (2 * half), level - 1);
}
}
解析册指出二维数组方案直观但空间很大,而且只适合这种可直接复制的图案;我的 E5 目录没有保留 I 题代码。
J. 博丽灵梦的大清洗
- 难度:6
- 考点:递归
- 可确认任务:对 1 到 的序列交替执行从上到下、从下到上的消去,输出最终留下的数字;完整操作描述与 范围未保留。
查看解析册的递归关系
解析册将大序列映射成规模减半的小序列,并利用两个方向的对称关系,得到:
int great_purge(int n)
{
if (n == 1) {
return 1;
}
return 2 * (n / 2 + 1 - great_purge(n / 2));
}
每层把 缩到 ,递归深度为 。这类题的关键不是模拟每次删除,而是找出“剩余序列怎样重新编号”以及相反方向之间的对称关系。
我的 E5 目录没有保留 J 题代码。
证据边界小结
- A~J 的题名、难度、考点和上述解法来自 E5 解析册;
- A~H 另有我的代码文件,可用于交叉核对实现轮廓;
- I、J 只有解析册示例;
- 原始题面、完整样例、限制与评测环境均未保留;
- 我故意不补写“输入格式”“输出格式”“数据范围”,也不把它放进真题目录。