洛谷 - P2005 A/B Problem II
题目链接
考察知识点
- 算法 - 二分
- 数学 - 高精度
思路分析
古话说得好:遇事不决,小学数学。
小学数学课本上对于乘法与除法之间的关系有着这样的定理:
除法是乘法的逆运算
本题即求ans
最大值,使得 $ ans \times N<=M $。
显然可以用二分去做。
注意到\(1 \le ans \le 13!\),因此要开long long。
时间复杂度
\(O(\log_{2}13! \times log_{10}N)\)
C++代码
#include <iostream> // 用于输入输出操作
#include <vector> // 用于存储大整数
using namespace std;
// 定义长整型别名,避免重复书写long long
typedef long long LL;
// 存储输入的两个大整数(字符串形式)
string a, b;
// 用vector存储大整数的每一位,逆序存放(低位在前,高位在后),方便处理进位和位运算
vector<int> A, B;
/*
比较两个大整数的大小(逆序存储)
若A >= B则返回true,否则返回false
*/
bool compare(vector<int> A, vector<int> B) {
// 若长度不同,长度更长的数更大
if (A.size() != B.size()) return A.size() > B.size();
// 长度相同则从最高位(vector的末尾)开始比较
for (int i = A.size() - 1; i >= 0; i--) {
if (A[i] != B[i]) {
return A[i] > B[i]; // 高位更大的数整体更大
}
}
// 所有位都相同,两数相等
return true;
}
/*
大整数乘法:大整数A(逆序)乘以一个小整数b(LL类型)
return 乘积结果(逆序存储的大整数)
*/
vector<int> mul(vector<int> &A, LL b) {
vector<int> C; // 存储乘积结果
LL t = 0; // 用于存储进位
// 遍历大整数A的每一位,或进位不为0时继续计算
for (int i = 0; i < A.size() || t; i++) {
// 累加当前位与b的乘积和进位
if (i < A.size()) t += A[i] * b;
// 当前位结果为t模10,存入C
C.push_back(t % 10);
// 更新进位(t除以10)
t /= 10;
}
// 去除结果中高位的0(因逆序存储,高位在末尾),保留至少一位
while (C.size() > 1 && C.back() == 0) {
C.pop_back();
}
return C;
}
int main() {
// 读取输入的两个大整数(字符串形式,避免“整型溢出”)
cin >> a >> b;
// 将字符串a转换为逆序的数字数组A(低位在前)
// 例如:"1000" 转换为 [0,0,0,1](个位0,十位0,百位0,千位1)
for (int i = a.size() - 1; i >= 0; i--) {
A.push_back(a[i] - '0'); // 字符转数字
}
// 同理,将字符串b转换为逆序的数字数组B
for (int i = b.size() - 1; i >= 0; i--) {
B.push_back(b[i] - '0');
}
// 二分查找的左右边界,寻找最大的mid使得 B * mid <= A(即mid = floor(A/B))
// 左边界初始为0,右边界6227020800是13的阶乘
LL l = 0, r = 6227020800;
// 二分查找循环,当l < r时继续
while (l < r) {
// 计算中间值mid,使用(l + r + 1)避免死循环(向上取整)
LL mid = (l + r + 1) / 2;
// 判断B * mid是否小于等于A:若A >= B*mid,则mid可以更大,移动左边界
if (compare(A, mul(B, mid))) {
l = mid;
} else {
// 否则mid太大,移动右边界
r = mid - 1;
}
}
// 输出找到的最大mid,即A/B的下取整结果
printf("%lld\n", l);
return 0;
}