NOI-1.3(19) A*B问题

这篇博客介绍了如何使用C++编程解决输入两个正整数并计算它们乘积的问题。程序中声明了无符号整数以避免溢出,并通过标准输入输出流 cin 和 cout 进行数据读取和结果展示。注意了数据类型的选取和乘积的范围,确保计算的正确性。

19:A*B问题

描述

输入两个正整数A和B,求A*B。

输入

一行,包含两个正整数A和B,中间用单个空格隔开。1 <= A,B <= 50000。

输出

一个整数,即A*B的值。

示例输入

3 4

示例输出

12

提示

注意乘积的范围和数据类型的选择

分析

题目要求是正整数的,所以要声明为无符号整数。

数值型和字符型数据的字节数和数值范围

类型 类型标识符 字节数 数值范围 整型 [ signed ] int 4  -2147483648 ~ +2147483647 无符号整型 unsigned [ int ] 4  0 ~ 4294967295 短整型 short [ int ] 2  -32768 ~ +32767 无符号短整型 unsigned short [ int ] 2  0 ~ 65535 长整型 long [ int ] 4  -2147483648 ~ +2147483647 无符号长整型 unsigned long [ int ] 4  0 ~ 4294967295 字符型 [ signed ] char 1  -128 ~ +127 无符号字符型 unsigned char 1  0 ~ 255 单精度型 float 4 3.4*10^{-38} ~ 3.4*10^{38} 双精度型 double 8 1.7*10^{-308} ~ 1. 7*10^{308} 长双精度型 long double 8 1.7*10^{-308} ~ 1.7*10^{308}

代码

#include <iostream>

using namespace std;

int main()
{
	unsigned int A, B;	//声明正整数A,B
	cin >> A >> B;
	cout << A * B << endl;
	return 0;
}

<think>我们正在为洛谷P1731 [NOI1999] 生日蛋糕的代码添加注释。 题目要:制作一个体积为Nπ,M层的生日蛋糕,每层都是一个圆柱体。从下往上数第i层蛋糕的半径为Ri,高度为Hi,且Ri>Ri+1且Hi>Hi+1。为了使蛋糕外表面面积最小,我们需要在满足体积要的情况下,寻找表面积最小的方案(除下底面外,其他部分的表面积称为外表面面积,这里我们实际上只考虑侧面积顶面积,因为题目要最小化的是外表面面积,而最下一层的下底面不需要考虑,但是注意:顶面积其实就是上一层的下底面,所以实际上我们只需要计算侧面积之,以及最顶层的上表面面积?然而,题目要的是外表面面积,实际上,每一层的侧面面积顶面面积(即上一层的下底面)都是暴露的,但注意最底层的下底面是不算的,因为放在桌子上。然而,在计算中,我们通常将整个蛋糕的顶部面积(即最上面一层的上表面)侧面积相加。但是,如果我们从上往下做,那么每一层的上表面(即上一层的下底面)会被下一层覆盖,所以实际上暴露在外的只有最顶层的上表面每一层的侧面积。因此,总的外表面积S等于最顶层的面积(π*r_m^2)加上每一层的侧面积(2*π*r_i*h_i)之。 但是,在题目中,我们不需要考虑π,所以实际计算中,我们只计算数值部分:最顶层的面积(r_m^2)加上每一层的侧面积(2*r_i*h_i)之。注意,题目要的是最小表面积(实际上,我们计算的是最小表面积对应的数值,不包括π)。 然而,在代码中,我们注意到: 在dfs中,我们计算当前层时,k表示当前已经计算的总表面积(不包括底面积,注意代码中最后在返回时加上了第一层的底面积)。为什么这样呢? 我们看dfs的终止条件:当x==m+1且y==0(即体积用完)时,我们执行:k += r[1]*r[1]; 然后更新minn。 这是因为,在dfs过程中,我们每加一层,我们计算的是侧面积(2*r_i*h_i),而整个蛋糕的上表面(即最顶层的上表面)在dfs过程中没有被计算,但是实际上,最顶层的上表面就是最上面那层的圆面积,即r[m]^2。然而,在代码中,我们并没有在dfs过程中加上这个顶面积。那么为什么最后加的是r[1]*r[1]呢? 这里需要理解:在dfs过程中,我们是从下往上(或者说是从第一层到第m层)递归的。但是,在题目中,我们通常将最底层作为第一层。那么最顶层的上表面就是第m层的上表面,但是我们在递归到第m层时,并没有加上这个上表面。然而,在终止条件中,我们加上的是r[1]*r[1](第一层的半径的平方),这显然是不对的。 重新审视题目:题目要外表面面积,而最底层的下底面是不算的,但是最顶层的上表面是要算的。另外,每一层的侧面积都要算。所以,总表面积S = 最顶层的上表面(即第m层的上表面) + 每一层的侧面积(2*r_i*h_i)之。 然而,在代码中,我们并没有在dfs过程中加上最顶层的上表面。那么,为什么最后加的是第一层的底面积呢?这似乎与题目的要不符。 实际上,我们观察代码,在dfs的过程中,我们计算的是侧面积:k + (i*2*j),即2*r*h。那么,我们还需要加上最顶层的上表面(即第m层的上表面)?但是,在终止条件中,我们加的是r[1]*r[1](第一层的半径的平方),这是为什么呢? 这里有一个重要的技巧:在蛋糕的整个外表面中,如果我们从顶向下看,顶部的面积等于最底层的投影面积?不对。实际上,最顶层的面积就是第m层的上表面,而第m层的上表面面积就是π*(r_m)^2。但是,我们也可以换一种思路:将整个蛋糕从上往下压扁,那么整个蛋糕的顶面投影实际上就是第一层(最底层)的投影,即π*(r_1)^2。这是因为,每一层的半径都比下一层小,所以最顶层的上表面会被包含在最底层的投影内。但是,我们实际暴露的顶面只有最上面那层,所以顶面面积应该是π*(r_m)^2。所以,显然代码中加r[1]*r[1]是不对的。 然而,我们再看题目:题目要的是外表面面积,而最底层的下底面是放在桌子上不计算的,所以实际暴露的顶面就是最顶层的上表面。但是,如果我们把整个蛋糕倒过来(即翻转),那么最顶层就变成了最底层,放在桌子上,而原来的最底层变成了最顶层。这样,我们计算的外表面面积就变成了:原来的最顶层的上表面(现在作为最底层的下底面)被桌子遮住,而原来的最底层的下底面(现在作为最顶层的上表面)暴露在外。所以,翻转后,暴露的顶面面积就是原来最底层的下底面面积,即π*(r_1)^2。因此,整个蛋糕的外表面积就是:所有层的侧面积之(翻转前后不变)加上原来最底层的下底面面积(即翻转后最顶层的上表面面积)π*(r_1)^2。 因此,在代码中,我们在最后加上r[1]*r[1](即第一层的半径平方)实际上就是加上了翻转后最顶层的上表面面积。所以,这样计算是正确的。 但是,为什么我们不在dfs过程中加上最顶层的上表面呢?因为我们在dfs过程中,每一层只计算侧面积,而顶面积我们统一在最后用第一层的底面积来代替(即翻转思想)。这样,我们就不需要在递归过程中考虑每一层的上表面了(因为每一层的上表面在翻转后都变成了下一层的下底面,而下一层的下底面会被上一层覆盖,所以只有最顶层的上表面暴露,而这个最顶层的上表面就是翻转前最底层的下底面,即第一层的底面积)。 因此,代码中的处理是合理的。 现在,我们为代码添加注释: 变量说明: n: 总体积(体积Nπ,所以实际体积为n,因为π可以约去) m: 层数 r[30]: 记录每一层的半径,r[0]初始化一个最大值(即sqrt(n)),因为半径最大不可能超过sqrt(n)(当只有一层时,体积n=π*r^2*h,取h=1,则r最大为sqrt(n)) h[30]: 记录每一层的高度 minn: 记录最小表面积,初始化为很大的数(2147483647) 函数dfs(int x, int y, int k, int z) 参数说明: x: 当前层数(从1开始,到m+1层时结束,因为总共有m层) y: 剩余的体积 k: 当前已经计算的总侧面积(不包括任何底面积或顶面积) z: 剩余层数(从m开始,逐层减1) 注意:在dfs中,我们首先进行剪枝: 1. 如果剩余体积y<0,直接返回。 2. 如果当前层数x>m+1(即已经超过m层),返回。 3. 如果当前总侧面积k已经大于等于minn,则返回(因为再继续也不会得到更小的表面积)。 4. 如果剩余体积为0且层数正好为m+1(即已经用完m层),那么此时我们加上第一层的底面积(即r[1]*r[1])作为整个蛋糕的顶面积,然后更新minn,返回。 5. 如果当前已经计算的总侧面积k加上剩余部分的最小可能侧面积(即z)再加上第一层的底面积(r[1]*r[1])已经大于minn,则返回(这个剪枝不准确,实际上z在这里表示剩余层数,但代码中z-1传递,所以z表示剩余层数。但是,这里剪枝条件写的是:k+z+r[1]*r[1]>minn。这里z代表什么?实际上,在调用dfs时,z的初始值为m,然后每次递归z-1。所以,在每一层,z的值是剩余层数。但是,这个剪枝条件中,z被当作一个侧面积的估计值?显然不对。所以,这个剪枝可能是错误的。实际上,后面有一个更合理的剪枝:用剩余体积当前层的最大可能体积比较。) 6. 剪枝:如果剩余体积y大于当前层(第x层)用最大可能半径(r[x-1]-1最大可能高度(h[x-1]-1)所能构成的最大体积(即(r[x-1]-1)^2*(h[x-1]-1)乘以剩余层数z(这里z是剩余层数,但这样乘没有道理)?这个剪枝条件:if(y-(r[x-1])*(r[x-1])*(h[x-1])*z>0) return; 解释:这个剪枝是说,如果剩余的体积y大于(用上一层(x-1层)的半径高度(因为当前层的半径高度不能超过上一层的减1)所能构成的最大体积乘以剩余层数(z层)),那么剩余体积太大,不可能在剩余层数内用完,所以返回。但是,这里乘以z层是不对的,因为每一层最多只能使用(r[x-1]-1)^2*(h[x-1]-1)的体积?实际上,每一层最多能使用的体积是递减的,而且每一层使用的体积不能超过剩余体积。所以,这个剪枝条件应该是:剩余体积y大于剩余z层所能构成的最大总体积。而剩余z层的最大总体积是多少?我们可以用当前层(x层)的最大可能半径(即r[x-1]-1最大可能高度(h[x-1]-1)来算,但是注意,每一层的半径高度都是递减的,所以下一层的最大半径高度不能超过上一层的半径高度减1。因此,从当前层到最后一层,最大体积是递减的。所以,我们可以估计剩余z层的最大体积为:z * (r[x-1]-1)^2*(h[x-1]-1) (因为每一层都可以取最大半径最大高度,而且每一层都取最大值,所以总体积最大为z*(当前层最大体积))。但是,这个估计是偏大的,因为越往后半径高度越小。所以,如果剩余体积y大于这个最大值,那么肯定不可能用完,所以剪枝。但是,条件中写的是:y - (r[x-1])*(r[x-1])*(h[x-1])*z > 0,这里用的是上一层的半径高度(即r[x-1]h[x-1])而不是减1后的值,所以这个剪枝条件应该是:y > (r[x-1]^2 * h[x-1]) * z,即剩余体积大于用上一层半径高度计算的体积乘以剩余层数。但这样计算是不准确的,因为当前层的半径高度最大只能是上一层的减1,所以应该用减1后的值。而且,这个剪枝条件应该是:y > 从当前层到最后一层所能构造的最大总体积(即当前层取最大半径最大高度,然后下一层在此基础上减1,直到最后一层)。但是,这样计算很复杂,所以代码中采用了一个近似:用当前层最大可能体积(即(r[x-1]-1)^2*(h[x-1]-1))乘以剩余层数z,但是这里却用了没有减1的值(即r[x-1]h[x-1])?所以这个剪枝条件可能是错误的。 实际上,我们观察循环:循环变量i从r[x-1]-1递减到z(z是剩余层数),j从h[x-1]-1递减到z。所以,当前层的最小半径高度至少为z(因为后面还有z层,每一层至少半径高度为1,所以当前层至少为z?不对,因为后面每一层至少比前一层小1,所以当前层的半径高度至少为z(因为后面还有z层,每一层递减1,所以最后一层至少为1,那么当前层至少为z))。所以,循环的下界是z。 然后,我们枚举当前层的半径i高度j,然后递归到下一层(x+1层),剩余体积为y-i*i*j,当前总侧面积k加上2*i*j(因为侧面积是2*π*r*h,这里忽略π,所以是2*r*h),剩余层数为z-1。 在递归返回后,要重置r[x]h[x]为0(回溯)。 主函数中: 初始化r[0]h[0]为sqrt(n)(取整),因为第一层的半径高度最大可能为sqrt(n)(当只有一层时,体积n=π*r^2*h,取h=1,则r最大为sqrt(n))。 然后调用dfs(1, n, 0, m) 表示从第一层开始,剩余体积为n,当前总侧面积为0,剩余层数为m。 最后,如果minn没有被更新(即还是2147483647),则输出0(表示无解),否则输出minn。 但是,我们注意到,在剪枝条件中有一个条件是:if(k+z+r[1]*r[1]>minn) return; 这里z是剩余层数,而k是当前总侧面积,加上r[1]*r[1](最后要加的顶面积)剩余层数z(这个z是整数,代表层数,不是面积)?这显然不合理。所以,这个剪枝条件可能是写错了。实际上,我们可能想用剩余部分的最小可能侧面积来剪枝,但是这里直接用了z(剩余层数)作为估计值,这显然是不对的。 因此,我们需要修正这个剪枝条件?但题目要加注释,所以我们先按照原代码加注释。 现在,我们为代码添加详细注释:</think>以下是已添加详细注释的洛谷P1731生日蛋糕问题C++代码: ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<cmath> using namespace std; int r[30]; // 存储每层半径,r[0]初始化为最大可能半径 int h[30]; // 存储每层高度,h[0]初始化为最大可能高度 int minn = 2147483647; // 初始化最小表面积为最大值 int n, m; // n:总体积,m:总层数 // DFS搜索函数 // x: 当前层数(1开始计数) // y: 剩余可用体积 // k: 当前累计侧面积(不含顶面) // z: 剩余层数(初始为m) void dfs(int x, int y, int k, int z) { // 剪枝1: 剩余体积为负,无效路径 if (y < 0) return; // 剪枝2: 超过总层数限制 if (x > m + 1) return; // 剪枝3: 当前累计面积已超过最小值 if (k >= minn) return; // 终止条件: 所有层处理完毕且体积恰好用完 if (y == 0 && x == m + 1) { k += r[1] * r[1]; // 加上底层顶面面积(即蛋糕最顶面) if (k < minn) minn = k; // 更新最小表面积 return; } // 剪枝4: 当前累计面积+剩余层数估计+顶面面积已超过最小值 if (k + z + r[1] * r[1] > minn) return; // 剪枝5: 剩余体积超过剩余层数最大可能体积 if (y - (r[x - 1]) * (r[x - 1]) * (h[x - 1]) * z > 0) return; // 枚举当前层的半径高度(从大到小枚举) for (int i = r[x - 1] - 1; i >= z; i--) { // 半径至少为剩余层数z for (int j = h[x - 1] - 1; j >= z; j--) { // 高度至少为剩余层数z if (y - i * i * j >= 0 && x + 1 <= m + 1) { // 体积合法且层数有效 r[x] = i; // 记录当前层半径 h[x] = j; // 记录当前层高度 // 递归下一层: // x+1: 层数增加 // y-i*i*j: 减去当前层体积 // k+(2*i*j): 增加当前层侧面积(2πrh省略π) // z-1: 剩余层数减少 dfs(x + 1, y - i * i * j, k + (2 * i * j), z - 1); // 回溯重置 h[x] = 0; r[x] = 0; } } } } int main() { scanf("%d%d", &n, &m); // 输入总体积总层数 // 初始化第0层尺寸(作为第一层的上限) r[0] = (int)sqrt(n); // 最大半径取sqrt(n) h[0] = (int)sqrt(n); // 最大高度取sqrt(n) // 开始DFS搜索 // 参数1: 从第1层开始 // 参数2: 剩余体积为n // 参数3: 初始侧面积为0 // 参数4: 剩余层数为m dfs(1, n, 0, m); // 输出结果 if (minn == 2147483647) printf("%d", 0); // 无解输出0 else printf("%d", minn); // 输出最小表面积 return 0; } ``` ### 代码解析: 1. **变量说明**: - `r[30]`, `h[30]`:存储每层蛋糕的半径高度,`r[0]`/`h[0]`初始化为最大可能值 - `minn`:记录最小表面积,初始化为`INT_MAX` - `n`:蛋糕总体积(对应题目中的$N\pi$,省略$\pi$) - `m`:蛋糕总层数 2. **DFS参数**: - `x`:当前处理层数(从1开始) - `y`:剩余可用体积 - `k`:当前累计侧面积(不含顶面) - `z`:剩余待处理层数 3. **剪枝策略**: - **体积溢出剪枝**:剩余体积为负时终止 - **层数溢出剪枝**:超过总层数时终止 - **最优性剪枝1**:当前累计面积≥已知最小值时终止 - **最优性剪枝2**:当前面积+剩余层数估计+顶面面积≥已知最小值时终止 - **可行性剪枝**:剩余体积 > 剩余层数最大可能体积时终止 4. **枚举策略**: - 半径/高度从大到小枚举(优先尝试大面积方案) - 半径/高度的下限为剩余层数$z$(确保后续层有足够空间递减) - 回溯时重置当前层状态 5. **表面积计算**: - 侧面积 = $2 \times \pi r h$(代码中省略$\pi$) - 顶面积 = 底层半径的平方(对应蛋糕最顶面) 6. **输出处理**: - 无解时输出$0$ - 有解时输出最小表面积 ### 算法核心: 通过DFS+强剪枝枚举每层半径高度,关键优化包括: 1. **从大到小枚举**:优先尝试大面积方案,便于快速剪枝 2. **多层剪枝**:结合体积、面积、层数的数学关系提前终止无效分支 3. **极限值估计**:利用剩余层数计算半径/高度下限体积上限
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值