大數乘法(困難版) 題解
簡潔題意
給兩個最多 \(4 \times 10^4\) 位、沒有前導零的非負整數 \(x\) 與 \(y\),輸出 \(x \times y\)。跟非困難版的差別只有一個:時限 \(0.25\) 秒。
解法
每位一格為什麼不夠用
15.9 的大數乘法(也就是課堂版大數乘法的做法)每位一格,歸戶要跑滿 \(n \times m\) 次。這題 \(40000 \times 40000 = 1.6 \times 10^9\) 次——本站實測要 \(1.1\) 秒,\(0.25\) 秒的時限就是衝著它來的。
壓位:一格存 6 位
省時間的方向不是換演算法,而是換進制:把數字改寫成 \(10^6\) 進制——由右往左每 \(6\) 個字元切成一格(跟 15.2 二進制轉十六進制「從右往左每 \(4\) 位一組」是同一件事),每格放 \(0 \sim 999999\)。位值系統的規則不因進制而變(15.1),所以 15.9 的歸戶、15.8 的統一進位整套照搬,只是 \(10\) 全換成 \(10^6\):
C[i + j] += A[i] * B[j]; // 歸戶:位值 BASE^i × BASE^j = BASE^(i+j)
C[i + 1] += C[i] / BASE; // 統一進位
C[i] %= BASE;
格數縮成 \(\frac{1}{6}\),兩層迴圈的次數縮成 \(\frac{1}{36}\):\((40000 / 6)^2 \approx 4.5 \times 10^7\) 次,本站實測 \(0.04\) 秒。
為什麼是 6 位
這是 15.6 那套估算的實戰:歸戶的單格累加上看「格數 \(\times\) 單筆乘積」\(\approx 6700 \times (10^6)^2 = 6.7 \times 10^{15}\),long long(約 \(9.2 \times 10^{18}\))撐得住。貪心壓到 \(8\) 位就翻車了:\(5000 \times (10^8)^2 = 5 \times 10^{19}\),溢位。壓位的甜蜜點就是「格子越大迴圈越少,但單格累加不能爆」。
輸出的新坑
以格為單位去掉前導零之後,最高格照常印,其餘每格都要補滿 \(6\) 位。拿 \(1000000 \times 1\) 想:格子是 \(\{0, 1\}\),最高格印 1,低位那格的 \(0\) 要印成 000000,答案才是 1000000——直接印 0 會輸出 10。低位格裡的前導零是位值的一部分,少印一個就整個錯位。
常犯錯誤
- 沿用每位一格(TLE):\(1.6 \times 10^9\) 次歸戶,\(0.25\) 秒做不完
- 中間格沒補滿 \(6\) 位(WA):低位格的值不足 \(6\) 位時,前導零是位值的一部分,不能省
- 壓 \(8\) 位、\(9\) 位(WA):單格累加爆
long long——小測資抓不到,位數夠多才爆,最難查 - 切格子從左往右切(WA):位數不是 \(6\) 的倍數時位值全錯,跟二進制轉十六進制沒左補 \(0\) 是同一個錯
參考程式碼
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const long long BASE = 1000000; // 一格存 6 位:把數字當成 10^6 進制
// 由右往左每 6 個字元切一格(個位那格住在索引 0)
vector<long long> toLimbs(const string& s) {
vector<long long> d;
for (int end = s.size(); end > 0; end -= 6) {
int start = max(0, end - 6);
long long v = 0;
for (int i = start; i < end; i++) v = v * 10 + (s[i] - '0'); // 15.3 的累積式
d.push_back(v);
}
return d;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string a, b;
cin >> a >> b;
vector<long long> A = toLimbs(a);
vector<long long> B = toLimbs(b);
int n = A.size(), m = B.size();
vector<long long> C(n + m, 0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
C[i + j] += A[i] * B[j]; // 歸戶(同 15.9,基底換成 10^6)
}
}
for (int i = 0; i + 1 < (int)C.size(); i++) { // 統一進位(同 15.8)
C[i + 1] += C[i] / BASE;
C[i] %= BASE;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); // 去前導零(以格為單位)
cout << C.back(); // 最高格照常印
for (int i = (int)C.size() - 2; i >= 0; i--) {
string t = to_string(C[i]);
cout << string(6 - t.size(), '0') << t; // 其餘每格補滿 6 位
}
cout << '\n';
return 0;
}
複雜度:\(O\left(\frac{n}{6} \times \frac{m}{6}\right)\)。