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