非官方题解 | ACGO欢乐赛#67
2026-02-15 22:24:47
发布于:广东
前言:
请大家放心看题解,虽然作者这次“破天荒”没有AK欢乐赛(因为我的浮木让我先把《匆匆》背完再去打比赛),但是这份题解是作者花了大量时间写的。
T1 皓仔的存钱罐
一、题目理解
你需要解决的问题是:给定存钱罐里一元硬币的总数 n,计算最多能兑换成多少张100元的纸币。核心就是求 n 中有多少个完整的100,只需要取整数部分即可。
二、解题思路
- 核心逻辑:因为每张纸币需要100个一元硬币,所以能兑换的纸币数量 = 总硬币数 ÷ 100(取整数部分)。
- 例如:150 ÷ 100 = 1.5 → 取整数部分1;200 ÷ 100 = 2 → 取整数部分2。
- 实现方式:在C++中,整数除以整数会自动舍去小数部分,直接得到整数结果,因此可以直接用
/运算符计算。
三、完整代码(带详细注释)
// 包含所有常用头文件,简化代码编写
#include <bits/stdc++.h>
// 使用std命名空间,避免重复写std::
using namespace std;
int main() {
// 定义整型变量n,存储输入的硬币总数(一元硬币的数量)
int n;
// 从控制台读取输入的整数n
cin >> n;
// 计算能兑换的100元纸币数量:整数除法自动舍去小数部分
int m = n / 100;
// 输出结果
cout << m;
// 程序正常结束
return 0;
}
四、代码逐行解释
#include <bits/stdc++.h>:这是C++的万能头文件,包含了所有常用的标准库头文件(如输入输出、数据结构等),新手使用可以避免漏写头文件的问题。using namespace std;:使用标准命名空间,这样后续使用cin、cout等时不需要加std::前缀。int main():程序的主函数,是程序执行的入口。int n;:声明一个整型变量n,用于存储输入的硬币总数(因为硬币是一元的,所以n也代表总金额)。cin >> n;:从控制台读取用户输入的整数,赋值给变量n。int m = n / 100;:核心计算逻辑,用总金额n除以100,因为是整数除法,结果会自动舍去小数部分,得到最多能兑换的纸币数量。- 例如:输入150时,150/100=1;输入200时,200/100=2;输入999时,999/100=9。
cout << m;:将计算结果m输出到控制台。return 0;:主函数返回0,表示程序正常执行结束。
五、测试用例验证
| 输入 | 计算过程 | 输出 | 说明 |
|---|---|---|---|
| 150 | 150 ÷ 100 = 1 | 1 | 150元最多换1张100元,剩余50元不够换 |
| 200 | 200 ÷ 100 = 2 | 2 | 200元刚好换2张100元 |
| 999 | 999 ÷ 100 = 9 | 9 | 999元最多换9张100元,剩余99元不够换 |
| 10000 | 10000 ÷ 100 = 100 | 100 | 10000元刚好换100张100元 |
六、注意事项
- 数据范围:题目说明
100 ≤ n ≤ 10000,int类型的取值范围远大于这个区间,因此无需担心溢出问题。 - 整数除法特性:C++中整数相除时,结果只保留整数部分,小数部分直接丢弃(不是四舍五入),这正好符合题目“最多能换几张”的要求。
总结
- 本题核心是利用整数除法舍去小数部分的特性,计算总金额中包含多少个完整的100元。
- 代码实现的关键步骤:读取输入的总金额 → 用总金额除以100 → 输出结果。
- 新手需要掌握
cin(输入)、cout(输出)的基本用法,以及整数除法的特性。
T2 皓仔买水果
一、题目深度理解
你需要解决的核心问题是:皓仔有 n 元钱,只能选择苹果、梨子、桃子中的一种水果购买(不能混合买),三种水果单价分别为 a、b、c 元。要找到一种购买方式,让花完钱后剩下的金额最少,最终输出这个“最少剩余金额”。
举个例子:输入 10 4 7 6 时,
- 买苹果:10元最多买2个(花8元),剩余
10 - 2×4 = 2元; - 买梨子:10元最多买1个(花7元),剩余
10 - 1×7 = 3元; - 买桃子:10元最多买1个(花6元),剩余
10 - 1×6 = 4元;
最少剩余金额是2元,这就是最终答案。
二、解题核心思路
- 剩余金额的计算方式:
购买单价为x的水果时,剩余金额 = 总钱数n取余 单价x(即n % x)。- 取余运算
%的本质:求n除以x后剩下的余数,正好对应“花最多钱买该水果后剩下的钱”。 - 例如:
10 % 4 = 2、10 % 7 = 3、10 % 6 = 4,完全匹配上面的例子。
- 取余运算
- 最终目标:
计算买苹果、梨子、桃子三种情况的剩余金额,再取这三个值的最小值。
三、完整代码(带逐行注释)
// 万能头文件:包含C++所有常用标准库,无需单独引入iostream等
#include <bits/stdc++.h>
// 使用std命名空间:避免写std::cin、std::cout等冗余代码
using namespace std;
int main() {
// 定义变量:n=总钱数,a=苹果单价,b=梨子单价,c=桃子单价
// 用long long而非int:题目中数值范围达10^9,int最大值约2×10^9(勉强够),long long更安全(最大值9×10^18)
long long n, a, b, c;
// 读取输入:依次输入4个整数,赋值给n、a、b、c
cin >> n >> a >> b >> c;
// 计算每种水果购买后的剩余金额
long long pg = n % a; // 买苹果剩余的钱
long long li = n % b; // 买梨子剩余的钱
long long tz = n % c; // 买桃子剩余的钱
// 找三个剩余金额的最小值:先找li和tz的最小值,再和pg比
long long mi = min(pg, min(li, tz));
// 输出最少剩余金额
cout << mi;
// 程序正常结束
return 0;
}
四、代码关键细节解释
-
数据类型选择(long long):
题目明确说明1 ≤ n,a,b,c ≤ 10^9:int类型的取值范围是-2^31 ~ 2^31-1(约-2×10^9 ~ 2×10^9),虽然能容纳10^9,但如果后续有扩展计算(比如n*a)可能溢出;long long类型取值范围是-2^63 ~ 2^63-1(约-9×10^18 ~ 9×10^18),完全覆盖题目范围,是工业界的最佳实践。
-
取余运算
%的正确性:
取余运算的数学定义是:n = k×x + r(其中k是整数,0 ≤ r < x),这里的r就是n%x。k对应“最多能买的水果数量”,r对应“剩余的钱”,正好符合题目“尽量花完钱”的要求。- 极端情况:若
n是x的整数倍(如n=20, x=5),则n%x=0,代表钱能全部花完,剩余0元(最优解)。
-
最小值的计算方式:
代码中min(pg, min(li, tz))是分步求最小值的写法:- 第一步:
min(li, tz)找到买梨子和桃子的最小剩余金额; - 第二步:再和
pg比较,找到三者的最小值。
也可以用C++11及以上支持的简化写法:min({pg, li, tz}),效果完全一致。
- 第一步:
五、多组测试用例验证
| 输入 | 计算过程(剩余金额) | 最小值 | 输出 | 说明 |
|---|---|---|---|---|
| 10 4 7 6 | 苹果=2、梨子=3、桃子=4 | 2 | 2 | 样例输入,买苹果剩余最少 |
| 20 5 8 10 | 苹果=0、梨子=4、桃子=0 | 0 | 0 | 买苹果/桃子能花完所有钱 |
| 15 7 9 11 | 苹果=1、梨子=6、桃子=4 | 1 | 1 | 买苹果剩余最少 |
| 999999999 1 2 3 | 苹果=0、梨子=1、桃子=0 | 0 | 0 | 买苹果/桃子能花完钱 |
六、边界情况考虑
- n < 单价:比如输入
5 6 7 8,此时5%6=5、5%7=5、5%8=5,输出5(只能不买,剩余全部钱); - 单价=1:比如输入
100 1 2 3,100%1=0,输出0(能买100个苹果,花完所有钱); - n=单价:比如输入
10 10 20 30,10%10=0,输出0(买1个苹果,花完钱)。
总结
- 核心逻辑:剩余金额 = 总钱数 % 水果单价,最终取三个剩余值的最小值;
- 关键细节:用
long long避免数值溢出,%运算符精准计算剩余金额; - 代码特点:简洁高效,时间复杂度为
O(1)(仅固定次数的算术运算),能处理题目所有数据范围。
T3 皓仔的车辆测试
一、题目深度理解
你需要解决的核心问题是:给定 n 个采样点的高度,先计算每两个相邻采样点的高度差的绝对值(即“颠簸量”),再求所有颠簸量的平均值,最终将平均值四舍五入保留2位小数输出。
举个例子(对应样例输入):
- 采样点数量
n=5,高度为3, 1, -1, 6, 4; - 相邻颠簸量计算:
- 第1段:|1-3|=2;
- 第2段:|-1-1|=2;
- 第3段:|6-(-1)|=7;
- 第4段:|4-6|=2;
- 总颠簸量:2+2+7+2=13;
- 平均颠簸度:13 ÷ (5-1) = 3.25(正好保留2位小数)。
二、解题核心思路
- 关键公式推导:
- 相邻段数量 = 采样点数量 - 1(
n-1); - 总颠簸量 = 所有相邻采样点高度差的绝对值之和;
- 平均颠簸度 = 总颠簸量 ÷ 相邻段数量(
sum / (n-1))。
- 相邻段数量 = 采样点数量 - 1(
- 实现步骤:
- 读取采样点数量
n; - 遍历所有采样点,累加相邻高度差的绝对值得到总颠簸量;
- 计算平均值并按要求格式化输出。
- 读取采样点数量
三、完整代码(带逐行注释)
// 万能头文件:包含所有常用标准库,无需单独引入iostream、cmath等
#include <bits/stdc++.h>
// 使用std命名空间,简化cin、cout、abs等函数的调用
using namespace std;
int main() {
// 定义采样点数量n(int足够,因为n≤10^5 < int最大值)
int n;
cin >> n;
// 总颠簸量sum:用long long避免溢出(h_i范围±10^9,n≤10^5,sum最大10^14,远超int范围)
long long sum = 0;
// x存储前一个采样点高度,y存储当前采样点高度
long long x, y;
// 读取第一个采样点的高度,作为初始的“前一个高度”
cin >> x;
// 遍历剩余n-1个采样点(i从1到n-1)
for (int i = 1; i < n; i++) {
// 读取当前采样点高度
cin >> y;
// 累加当前段的颠簸量(高度差的绝对值)
sum += abs(y - x);
// 更新前一个高度为当前高度,为下一次循环做准备
x = y;
}
// 计算平均颠簸度:sum*1.0将整数转为浮点数,避免整数除法
double m = sum * 1.0 / (n - 1);
// 格式化输出:保留2位小数(自动四舍五入)
printf("%.2f", m);
// 程序正常结束
return 0;
}
四、代码关键细节解释
- 数据类型选择(核心避坑点):
n用int:n≤10^5,而int最大值约2×10^9,完全足够;sum/x/y用long long:- 单个高度差的绝对值最大为
|10^9 - (-10^9)| = 2×10^9; - 总颠簸量最大为
10^5 × 2×10^9 = 2×10^14,远超int最大值(2×10^9),必须用long long(最大值9×10^18)避免溢出。
- 单个高度差的绝对值最大为
- 循环逻辑优化:
- 先读取第一个采样点高度
x,再循环读取剩余n-1个点:- 避免重复读取数据,减少内存占用(无需存储所有高度,只需保存前一个值);
- 时间复杂度
O(n),空间复杂度O(1),适合n=10^5的大数据量。
- 先读取第一个采样点高度
- 浮点数计算与格式化输出:
sum * 1.0:将long long类型的sum转为浮点数,确保除法是“浮点数除法”而非“整数除法”(例如13/4若用整数除法得3,浮点数除法得3.25);printf("%.2f", m):%.2f表示保留2位小数;- 自动四舍五入(例如计算结果为3.249999,输出3.25;为3.254999,也输出3.25);
- 若用
cout实现,需添加fixed和setprecision(2)(需引入<iomanip>,但万能头已包含),写法为:cout << fixed << setprecision(2) << m;。
五、多组测试用例验证
| 输入 | 计算过程(总颠簸量/段数) | 平均值 | 输出 | 说明 |
|---|---|---|---|---|
| 5<br>3 1 -1 6 4 | (2+2+7+2)/4 = 13/4 = 3.25 | 3.25 | 3.25 | 样例输入,无四舍五入 |
| 3<br>10 5 8 | (5+3)/2 = 8/2 = 4.0 | 4.0 | 4.00 | 自动补0保留2位 |
| 4<br>-5 0 -3 7 | (5+3+10)/3 = 18/3 = 6.0 | 6.0 | 6.00 | 包含负数高度 |
| 2<br>1000000000 -1000000000 | (2000000000)/1 = 2e9 | 2000000000.00 | 2000000000.00 | 极端数值(验证long long有效性) |
六、边界情况与性能分析
- 边界情况:
n=2(最小采样点数量):仅1段颠簸量,直接输出该值÷1的结果;- 所有相邻高度相同:总颠簸量为0,输出0.00;
- 高度包含正负值:
abs()函数能正确计算绝对值,不受符号影响。
- 性能分析:
- 时间复杂度:
O(n),仅遍历一次采样点,适合n=10^5的数据规模; - 空间复杂度:
O(1),仅使用固定数量的变量,不占用额外内存。
- 时间复杂度:
总结
- 核心逻辑:总颠簸量 = 相邻高度差绝对值之和,平均颠簸度 = 总颠簸量/(n-1);
- 关键避坑点:用
long long存储高度和总颠簸量,避免数值溢出; - 输出技巧:用
printf("%.2f")或cout << fixed << setprecision(2)实现保留2位小数的格式化输出。
T4 皓仔的字母寻宝
一、题目深度理解
你需要解决的核心问题是:给定一个 n 行 m 列的字符矩阵,找出所有出现的大写字母(A~Z),并按 A到Z的顺序 输出每个字母的位置(行号、列号从1开始);若没有任何大写字母,输出 not found。
关键约束:
- 每个大写字母最多出现1次,无需处理重复;
- 输出必须严格按字母表顺序(即使矩阵中字母出现顺序混乱,也要按A→B→…→Z输出);
- 行号、列号从1开始计数(而非程序常用的0开始)。
二、解题核心思路
- 数据存储设计:
- 大写字母共26个(A~Z),可以用一个长度为26的结构体数组存储每个字母的信息:
- 是否存在(
e:存在为1,不存在为0); - 行号(
r); - 列号(
c)。
- 是否存在(
- 结构体数组下标与字母的对应关系:
A对应下标0,B对应下标1,…,Z对应下标25(通过ch - 'A'计算)。
- 大写字母共26个(A~Z),可以用一个长度为26的结构体数组存储每个字母的信息:
- 核心步骤:
- 初始化:将26个字母的“存在标记”设为0(不存在);
- 遍历矩阵:逐个字符检查,若为大写字母,记录其行号、列号,并将对应标记设为1;
- 输出结果:按A→Z的顺序(遍历0~25下标)检查标记,存在则输出,否则最后输出
not found。
三、完整代码(带逐行注释)
// 万能头文件:包含所有常用标准库(如iostream、string等)
#include <bits/stdc++.h>
using namespace std;
// 定义结构体:存储每个字母的位置和是否存在的标记
struct Pos {
int r; // 行号(从1开始)
int c; // 列号(从1开始)
bool e; // 存在标记:1=存在,0=不存在
} pos[26]; // 数组长度26,对应A(0)~Z(25)
int n, m; // 矩阵的行数n,列数m
int main() {
// 初始化:所有字母默认不存在(e=0)
for (int i = 0; i < 26; i++) {
pos[i].e = 0;
}
// 读取矩阵的行数和列数
cin >> n >> m;
string s; // 存储每行的字符
// 遍历每一行(行号从1开始)
for (int i = 1; i <= n; i++) {
cin >> s; // 读取第i行的字符
// 遍历该行的每一列(列号从1开始)
for (int j = 1; j <= m; j++) {
// 注意:字符串下标从0开始,所以第j列对应s[j-1]
char ch = s[j - 1];
// 判断是否为大写字母(A~Z)
if (ch >= 'A' && ch <= 'Z') {
int idx = ch - 'A'; // 计算字母对应的数组下标(A→0,B→1...)
pos[idx].r = i; // 记录行号
pos[idx].c = j; // 记录列号
pos[idx].e = 1; // 标记该字母存在
}
}
}
bool flag = 0; // 标记是否找到至少一个大写字母
// 按A→Z的顺序遍历(下标0~25)
for (int i = 0; i < 26; i++) {
// 如果该字母存在
if (pos[i].e) {
flag = 1; // 标记找到字母
char ch = 'A' + i; // 下标转回对应字母(0→A,1→B...)
// 输出:字母 行号 列号
cout << ch << " " << pos[i].r << " " << pos[i].c << endl;
}
}
// 若未找到任何大写字母,输出not found
if (!flag) {
cout << "not found";
}
return 0;
}
四、代码关键细节解释
-
结构体数组设计(核心):
- 为什么用长度26的数组?因为大写字母只有26个,下标与字母一一对应,能快速定位和按序输出;
e字段的作用:避免重复判断,同时标记是否存在该字母;- 初始化时将所有
e设为0,确保未出现的字母不会被误输出。
-
矩阵遍历的下标转换:
- 程序中矩阵的行号、列号从1开始(符合题目要求),但字符串的下标从0开始,因此第
j列的字符对应s[j-1]; - 例如样例输入1中,第一行字符串是
aB#d(下标0:a,1:B,2:#,3:d),列号2对应的字符是s[1],即B,与样例输出“B 1 2”一致。
- 程序中矩阵的行号、列号从1开始(符合题目要求),但字符串的下标从0开始,因此第
-
字母与下标转换:
- 大写字母转下标:
ch - 'A'(如'B' - 'A' = 1); - 下标转大写字母:
'A' + i(如'A' + 1 = 'B'); - 该转换利用了ASCII码的连续性(AZ的ASCII码是6590,连续递增)。
- 大写字母转下标:
-
输出顺序保证:
- 最终遍历下标025(对应AZ),而非按矩阵中字母出现的顺序,确保输出严格按字母表排序;
- 例如样例输入1中,矩阵中出现的字母是B、F、H,遍历下标时会先找到B(下标1),再找到F(下标5),最后找到H(下标7),与样例输出顺序一致。
五、测试用例验证
样例输入1
3 4
aB#d
eFgH
ijkl
执行过程:
- 初始化:pos[0]~pos[25]的e均为0;
- 遍历矩阵:
- 第1行(i=1):s="aB#d",j=2时ch='B',idx=1 → pos[1].r=1, pos[1].c=2, pos[1].e=1;
- 第2行(i=2):s="eFgH",j=2时ch='F',idx=5 → pos[5].r=2, pos[5].c=2, pos[5].e=1;j=4时ch='H',idx=7 → pos[7].r=2, pos[7].c=4, pos[7].e=1;
- 第3行(i=3):无大写字母;
- 输出:遍历0~25,依次输出B、F、H,与样例输出一致。
样例输入2
3 4
ab#d
efgh
ijkl
执行过程:
- 遍历矩阵无大写字母,flag保持0;
- 最终输出
not found,与样例输出一致。
六、边界情况与性能分析
- 边界情况:
- 矩阵中只有1个大写字母(如n=1,m=1,字符为Z):输出“Z 1 1”;
- 矩阵中包含所有26个大写字母:按A→Z顺序输出所有字母的位置;
- 矩阵行列数为最大值(1000×1000):代码仍能高效处理(时间复杂度O(n×m),1000×1000=1e6次循环,无性能问题)。
- 性能分析:
- 时间复杂度:O(n×m + 26),核心是遍历矩阵的O(n×m),后续遍历26个字母可忽略;
- 空间复杂度:O(26),仅使用固定大小的结构体数组,无额外内存消耗(无需存储整个矩阵,读取一行处理一行即可)。
总结
- 核心思路:用26长度的结构体数组映射A~Z,记录位置和存在状态,遍历矩阵填充信息,再按字母表顺序输出;
- 关键细节:行号/列号从1开始,需注意字符串下标与列号的转换(j-1);
- 输出要求:严格按A→Z顺序,而非矩阵中字母出现的顺序,通过遍历0~25下标实现。
T5 皓仔的字母占比
一、题目深度理解
你需要解决的核心问题是:给定十进制整数 n,在进制范围 [2, 36] 中找到一个进制 b,使得 n 转换为 b 进制后的字符串中,字母(A~Z)的占比 最大;若多个进制占比相同,选择数值更小的进制。
关键规则:
- 进制转换规则:0~9 用数字字符,10~35 用A~Z表示;
- 字母占比 = 字母字符数 / 总字符数;
- 特殊情况:
n=0时,所有进制下表示均为"0",字母占比为0,按要求输出最小进制2。
二、解题核心思路
- 核心步骤拆解:
- 对每个测试用例的
n,遍历所有进制b ∈ [2, 36]; - 对每个进制
b,调用toB函数将n转换为b进制字符串; - 统计字符串中字母(A~Z)的数量
lc,计算占比r = lc / 字符串长度; - 记录占比最大的进制
bb(占比相同时选更小的进制); - 输出最终选中的进制
bb。
- 对每个测试用例的
- 关键细节:
- 进制转换需处理
n=0的特殊情况(直接返回"0"); - 占比比较时需用浮点数,且需优先保留更小的进制(占比相同时)。
- 进制转换需处理
三、完整代码(保留原变量名 + 逐行注释)
#include <bits/stdc++.h>
using namespace std;
// 原变量名:n为待转换数,b为目标进制,返回b进制字符串
string toB(long long n, int b) {
// 特殊情况:n=0时所有进制表示为"0"
if (n == 0) return "0";
string r = ""; // 原变量名r:存储转换后的字符串结果
while (n > 0) {
int d = n % b; // 原变量名d:当前位的数码(余数)
if (d < 10) r = char(d + '0') + r; // 数码0-9转为数字字符
else r = char(d - 10 + 'A') + r; // 数码10-35转为字母字符
n /= b; // 去掉最后一位,继续处理高位
}
return r;
}
int main() {
int t; // 原变量名t:测试用例个数
cin >> t;
while (t--) {
long long n; // 原变量名n:输入的十进制数
cin >> n;
int bb = 2; // 原变量名bb:记录最优进制(初始为最小进制2)
double mr = 0.0; // 原变量名mr:记录最大字母占比(初始为0)
// 遍历所有候选进制(2~36)
for (int b = 2; b <= 36; b++) {
string s = toB(n, b); // 原变量名s:n的b进制字符串
int lc = 0; // 原变量名lc:字母(A~Z)的数量
for (int i = 0; i < s.size(); i++) { // 遍历字符串每个字符
if (s[i] >= 'A' && s[i] <= 'Z')
lc++;
}
double r = (double)lc / s.size(); // 原变量名r:当前进制的字母占比
// 更新最优进制:占比更大 或 占比相同但进制更小
if (r > mr || (r == mr && b < bb)) {
mr = r;
bb = b;
}
}
cout << bb << endl; // 输出最优进制
}
return 0;
}
四、代码关键细节解释(保留原变量名)
-
进制转换函数
toB(核心):n:传入的十进制数,b:目标进制,函数返回n的b进制字符串;d:每次取n%b得到的当前位数码,是进制转换的核心变量;r:拼接转换后的字符,通过新字符 + r保证高位在前(如35转36进制得到"Z");- 特殊处理
n=0:直接返回"0",符合题目规则。
-
主函数核心变量:
t:测试用例个数,通过while(t--)循环处理每个用例;bb:最终要输出的最优进制,初始值为2(最小进制,保证占比相同时选更小值);mr:记录遍历过程中遇到的最大字母占比,初始为0;lc:统计当前进制字符串中字母的数量,是计算占比的核心;r:当前进制的字母占比,需将lc转为double避免整数除法(如1/2=0而非0.5)。
-
最优进制选择逻辑:
- 条件
r > mr:当前进制占比更大,更新最大占比mr和最优进制bb; - 条件
r == mr && b < bb:占比相同但进制更小,更新最优进制(如多个进制占比为0时,保留初始值2)。
- 条件
五、样例输入验证(保留原变量名逻辑)
以样例输入 10 为例:
- 遍历
b=2:s="1010",lc=0,r=0.0→mr=0.0,bb=2; - 遍历
b=11:s="A",lc=1,r=1.0→mr=1.0,bb=11; - 后续进制(12~36)的
r均≤1.0,最终bb=11,与样例输出一致。
六、边界情况与性能
- 边界情况:
n=0:所有进制s="0",lc=0,r=0.0→bb=2;n=35:b=36时s="Z",lc=1,r=1.0→bb=36;
- 性能:
- 每个测试用例仅遍历35个进制(2~36),每个进制转换的时间为
O(log_b n)(最多约60次循环); - 即使
t=1e4,总循环次数仅3.5e5,无性能压力。
- 每个测试用例仅遍历35个进制(2~36),每个进制转换的时间为
总结(保留原变量名)
- 核心逻辑:通过
toB函数完成进制转换,遍历所有进制计算字母占比r,选择占比最大的进制bb; - 关键变量:
lc统计字母数,mr记录最大占比,bb记录最优进制; - 选择规则:占比优先(
r > mr),占比相同时选更小进制(b < bb)。
(作者在做这道题时把第4题的代码写到这题去了,结果全部RE)
T6 皓仔的手工课
一、题目深度理解
你需要解决的核心问题是:给定 n 根长度不同的绳子,要将每根绳子剪成若干段长度为 L 的等长小段(不足 L 的部分丢弃),要求最终至少得到 k 段,且 L 要尽可能大;若无论如何都无法得到 k 段,则输出 0。
关键规则:
- 每根绳子能剪出的段数 = 绳子长度
// L(整数除法,舍去余数); - 总段数 = 所有绳子剪出段数之和;
- 目标:找到满足“总段数 ≥ k”的最大
L。
二、解题核心思路
这是典型的二分查找问题,核心逻辑是“二分答案”:
- 答案范围确定:
L的最小可能值是1,最大可能值是所有绳子中的最长长度(超过这个长度的话,至少有一根绳子剪不出段,总段数必然不足); - 二分判断逻辑:对某个候选长度
mid,计算所有绳子能剪出的总段数:- 若总段数 ≥ k:说明
mid是可行解,尝试找更大的L(调整左边界); - 若总段数 < k:说明
mid不可行,需要找更小的L(调整右边界);
- 若总段数 ≥ k:说明
- 边界预处理:若所有绳子的总长度 < k(即使
L=1,总段数也不足k),直接输出0。
三、完整代码(保留原变量名 + 逐行注释)
#include <bits/stdc++.h>
using namespace std;
int n, k; // 原变量名:n=绳子数量,k=需要的最少段数
int a[101]; // 原变量名:存储每根绳子的长度(数组大小101适配n≤100)
int mx; // 原变量名:存储最长绳子的长度(二分右边界)
int sum; // 原变量名:存储所有绳子的总长度(预处理判断)
int res; // 原变量名:存储最终答案(最大可行L)
int main() {
// 步骤1:输入基本信息
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i]; // 读取第i根绳子的长度
mx = max(mx, a[i]); // 更新最长绳子长度
sum += a[i]; // 累加总长度
}
// 步骤2:预处理:总长度 < k → 即使L=1也不够,直接输出0
if (sum < k) {
cout << 0 << endl;
return 0;
}
// 步骤3:二分查找最大可行L
int l = 1, r = mx; // 二分左右边界:L最小1,最大为最长绳子长度
while (l <= r) {
// 计算中间值(避免l+r溢出,等价于(l+r)/2)
int m = l + (r - l) / 2;
int cnt = 0; // 原变量名:当前L=m时的总段数
// 计算所有绳子能剪出的总段数
for (int i = 1; i <= n; i++) {
cnt += a[i] / m; // 每根绳子的段数=长度//L
if (cnt >= k) break; // 提前终止:总段数已满足,无需继续计算
}
// 判断是否可行
if (cnt >= k) {
res = m; // 记录当前可行的L
l = m + 1; // 尝试找更大的L(调整左边界)
} else {
r = m - 1; // 当前L太大,找更小的L(调整右边界)
}
}
// 步骤4:输出结果
cout << res;
return 0;
}
四、代码关键细节解释(保留原变量名)
-
预处理逻辑(sum < k):
- 所有绳子总长度是能剪出的最大段数(当
L=1时,每1单位长度为1段); - 若总长度 < k,说明即使
L=1也凑不够k段,直接输出0,避免无效的二分查找。
- 所有绳子总长度是能剪出的最大段数(当
-
二分查找核心(l, r, m):
l=1:L的最小可能值(正整数);r=mx:L的最大可能值(超过最长绳子长度的话,至少有一根绳子剪不出段);m = l + (r - l)/2:等价于(l+r)/2,但避免了l+r超出int范围(本题数据范围小,两种写法均可,但前者是工业界最佳实践)。
-
总段数计算(cnt):
a[i]/m:整数除法,例如8/3=2(8长度的绳子剪3长度的段,能剪2段);if (cnt >= k) break:提前终止循环,减少计算量(比如前几根绳子就凑够了k段,无需计算剩余绳子)。
-
二分调整逻辑:
- 若
cnt >= k:m是可行解,记录到res,并尝试找更大的L(l = m + 1); - 若
cnt < k:m太大,需要缩小L(r = m - 1); - 循环结束时,
res存储的是最后一个可行的最大L。
- 若
五、样例输入验证(原变量名逻辑)
样例输入:5 11,绳子长度 8 9 8 6 7
- 预处理:
sum=8+9+8+6+7=38 ≥ 11,进入二分; - 初始边界:
l=1,r=9(最长绳子长度为9);- 第一次循环:
m=5,总段数=8/5+9/5+8/5+6/5+7/5=1+1+1+1+1=5 < 11 →r=4; - 第二次循环:
m=2,总段数=4+4+4+3+3=18 ≥11 →res=2,l=3; - 第三次循环:
m=3,总段数=2+3+2+2+2=11 ≥11 →res=3,l=4; - 第四次循环:
m=4,总段数=2+2+2+1+1=8 <11 →r=3; - 循环结束(
l=4 > r=3),最终res=3,与样例输出一致。
- 第一次循环:
六、边界情况与性能分析
-
边界情况:
- 总长度 < k:输入
3 10,绳子长度1 2 3→ sum=6 <10 → 输出0; - 刚好满足k段:输入
2 5,绳子长度5 5→ L=2时总段数=2+2=4 <5,L=1时总段数=5+5=10 ≥5 → 输出1; - 所有绳子长度相同:输入
3 6,绳子长度6 6 6→ L=3时总段数=2+2+2=6 → 输出3。
- 总长度 < k:输入
-
性能分析:
- 时间复杂度:二分循环次数为
log2(1000) ≈ 10次,每次循环遍历n≤100根绳子 → 总操作数≈1000,远小于时间限制; - 空间复杂度:仅使用固定大小的数组和变量 → O(n),满足内存限制。
- 时间复杂度:二分循环次数为
总结(保留原变量名)
- 核心思路:用二分查找找最大可行的
L,通过“判断候选L是否满足总段数≥k”缩小范围; - 关键变量:
mx确定二分右边界,sum预处理无效情况;m是候选L,cnt是总段数,res记录最优解;
- 优化点:计算
cnt时提前终止循环,减少不必要的计算。
最后:
我想跟大家说:“以后比赛要提前做!”
这里空空如也




















有帮助,赞一个