题解:洛谷 P2005 AB Problem II
【题目来源】
【题目描述】
给出正整数 \(N\) 和 \(M\),请你计算 \(\lfloor\frac{N}{M}\rfloor\)(\(\frac{N}{M}\) 的下取整)。
【输入】
两行,两个正整数,\(N\) 和 \(M\)。
【输出】
一行,一个整数,表示 \(\lfloor\frac{N}{M}\rfloor\)。
【输入样例】
1000
333
【输出样例】
3
【核心思想】
-
问题分析:给定两个正整数 \(N\) 和 \(M\),可能非常大(远超基本数据类型),需要计算 \(\lfloor N/M \rfloor\) 的整数部分。传统的高精度除以低精度算法(逐位竖式除法)仅适用于除数 \(M\) 为普通整数的情况;若 \(M\) 本身也是高精度数,则需采用更通用的方法。本题通过二分答案 + 高精度乘法与比较,将除法转化为在单调序列中寻找最大 \(mid\),使得 \(M \times mid \le N\),从而得到商。
-
算法选择:
- 二分答案:商的取值范围为 \([0, 10^{18}]\)(或根据输入数据范围确定),具有单调性,即若 \(M \times mid \le N\),则所有小于 \(mid\) 的值也满足。因此可以二分枚举 \(mid\)。
- 高精度乘法:将 \(M\) 存储为高精度数,乘以整数 \(mid\),得到 \(M \times mid\) 的高精度结果。
- 高精度比较:将乘积 \(M \times mid\) 与被除数 \(N\) 进行逐位比较,判断是否满足 \(\le N\)。
- 复杂度:二分次数约为 \(\log_2(10^{18}) \approx 60\) 次,每次进行高精度乘法(\(O(L)\))和比较(\(O(L)\)),总复杂度 \(O(L \log C)\),其中 \(L\) 为 \(N\) 的位数,\(C\) 为商的上界。
-
关键步骤:
- 读入并存储:将 \(N\) 和 \(M\) 作为字符串读入,转换为高精度数组(低位在前,方便乘法和比较)。
- 二分查找:初始化左边界 \(l=0\),右边界 \(r=10^{18}\)(或根据数据范围调整,确保 \(r\) 足够大)。当 \(l < r\) 时,取 \(mid = \lceil (l+r)/2 \rceil\)(即
(l+r+1)//2),以避免死循环。 - 检查函数:计算 \(C = M \times mid\)(高精度乘法),然后比较 \(C\) 与 \(N\):
- 若 \(C\) 的位数小于 \(N\),或位数相同但 \(C\) 的每一位均小于等于 \(N\) 的对应位,则条件成立,令 \(l = mid\);
- 否则,令 \(r = mid-1\)。
- 输出结果:循环结束后,\(l\) 即为满足 \(M \times l \le N\) 的最大整数,即 \(\lfloor N/M \rfloor\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(L \log C)\),其中 \(L\) 是 \(N\) 的位数(高精度乘法与比较的代价),\(C\) 是二分搜索范围(通常为 \(10^{18}\),约 60 次迭代)。
- 空间复杂度:\(O(L)\),用于存储高精度数和中间结果。
-
二分 + 高精度乘法的核心思想:
- 单调性转化:求商等于求满足 \(M \times x \le N\) 的最大整数 \(x\),该性质使得二分法适用,避免直接进行复杂的高精度除法。
- 高精度乘法 + 比较:通过高精度乘法计算 \(M \times mid\),再与 \(N\) 逐位比较,实现精确判断。
- 通用性强:此方法不要求除数 \(M\) 为普通整数,可直接处理两个高精度数相除,只需实现高精度乘高精度即可(代码中为乘低精度,但思想可扩展)。
- 适用于大整数除法:当被除数和除数均超出基本数据类型时,二分+高精度乘法是一种可靠且易于实现的替代方案。
【算法标签】
普及 #高精度
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 20005;
struct BIG
{
int len, num[N];
BIG()
{// 构造函数:将高精度数字初始化为0
memset(num, 0, sizeof(num));
len = 1;
}
void set(int x)
{// 将int类型数字x转化为高精度数字
memset(num, 0, sizeof(num));
len = 0;
while (x>0)
{
num[++len] = x % 10;
x /= 10;
}
if (len==0) len = 1; // 特判0的情况
}
void set(string s)
{// 将string类型转化为高精度数字
memset(num, 0, sizeof(num));
len = s.size();
for (int i=1; i<=len; i++)
num[i] = s[len-i] - '0';
}
void print()
{// 输出高精度数字
for (int i=len; i>=1; i--)
cout << num[i];
cout << endl;
}
};
BIG operator*(BIG a, int b)
{
BIG c;
c.len = a.len;
int u = 0;
for (int i=1; i<=c.len; i++)
{
int t = a.num[i] * b + u;
c.num[i] = t % 10;
u = t / 10;
}
while (u>0)
{
c.num[++c.len] = u % 10;
u /= 10;
}
return c;
}
BIG A, B, C;
string s1, s2;
signed main() {
cin >> s1 >> s2;
A.set(s1);
B.set(s2);
int l = 0, r = 1e18;
while (l < r) {
int mid = (l + r + 1) / 2; // 注意:向上取整
cout << "mid " << mid << endl;
C = B * mid;
cout << "c " << endl;
C.print();
// 检查 b*mid 是否 ≤ a
bool condition = true;
if (C.num[0] > A.num[0]) {
// b*mid的位数比a多,肯定大于a
condition = false;
} else if (C.num[0] < A.num[0]) {
// b*mid的位数比a少,肯定小于a
condition = true;
} else {
// 位数相同,逐位比较
bool less = false, greater = false;
for (int i = C.num[0]; i >= 1; i--) {
if (C.num[i] < A.num[i]) {
less = true;
break;
} else if (C.num[i] > A.num[i]) {
greater = true;
break;
}
}
// 如果c == a 或 c < a,条件成立
condition = !greater; // 不大于就是小于等于
}
if (condition) {
l = mid; // 可以尝试更大的mid
} else {
r = mid - 1; // 太大了
}
}
cout << l << endl;
return 0;
}
【运行结果】
1000
333
3
浙公网安备 33010602011771号