OI+ACM 笔记:A - 基础算法
A - 基础算法 指令基础
NOI Linux 指令
返回上一级目录:cd ..
。
返回当前用户的主目录:cd ~
。
进入某一目录内:cd <目录路径>
。
显示当前工作目录:pwd
。
新建文件夹:mkdir <目录文件> <新建文件夹名>
。
删除:
- 删除单一文件:
rm <文件路径>
。 - 删除目录及目录内的文件:
rm -r <要删除的目录路径>
。
改名:mv <旧文件名> <新文件名>
。
查看文件内容:cat <文件路径>
。
简单查看文件信息:dir <目录路径>
。
详细查看文件信息:ls -alh <目录路径>
。
ls -a <目录路径>
:显示所有文件(包括隐藏文件)。ls -l <目录路径>
:显示文件属性。ls -h <目录路径>
:将文件大小转换为常用单位(无单位默认为字节)。
提升权限:
- 使用 root 权限(最高权限)操作该命令:
sudo <命令>
。 - 登录到另外一个用户:
su <用户名>
。
命令行编译:g++/gcc <源代码文件名> -o <可执行文件名>
。
-std=c++11
:c++11 标准。-O2
:O2 优化。ulimit -s 128000000
:开栈。-Wall
:显示所有警告。-Wextra
:检测可疑代码并生成警告。-Wconversion
:转型警告。
执行程序:./<应用程序>
。
只读执行程序:./<project> <A.in
。
只输执行程序:./<project> >A.out
。
读输执行程序:./<project> <A.in >A.out
。
查看执行程序运行时间:time ./<应用程序>
。
比较文本:diff <文本 A><文本 B>
。
A - 基础算法 语言基础
语言基础
进制
- 二进制 B,八进制 O,十进制 D,十六进制 H。
0x
表示十六进制。在 32 位有符号整数(int
)中,0xFFFFFFFF
表示 \(-1\)。- 使用
memset(a, value, sizeof(a))
初始化一个数组 \(a\),将数值 \(\mathrm{value}(\mathrm{0x00\sim 0xFF})\) 填充到数组 \(a\) 的每一个字节上。而 \(1\) 个int
占用 \(4\) 个字节,故memset
只能赋值出每 \(8\) 位都相同的int
。 0x3F3F3F3F
是满足以下两个条件的最大整数。常用于将一个数初始化为正无穷。- 整数的两倍不超过
0x7FFFFFFF
,即int
能表示的最大正整数。 - 整数的每 \(8\) 位(每个字节)都是相同的。
- 整数的两倍不超过
- 常使用
memset(a, 0x3f, sizeof(a))
,将一个数组 \(a\) 初始化为正无穷。
原码:
- 32 位有符号整数(
int
)的最高位为符号位。\(0\) 表示非负数,\(1\) 表示负数。
反码:
- 非负数的反码:为原码本身。
- 负数的反码:符号位不变,其余位取反。
补码:计算机中有符号数码用补码表示。解决了 \(0\) 的编码不唯一性。
- 非负数的补码:为原码本身。
- 负数的补码:符号位不变,其余位取反,再 \(+1\)(即在反码的基础上 \(+1\))。
- \(-2^{31}\) 的补码为
1000...0000
,无原码与反码。
数值类型
数值范围:
int
范围:\([-2^{31}, 2^{31})\)。unsigned int
范围:\([0, 2^{32})\)。long long
范围:\([-2^{63}, 2^{63})\)。unsigned long long
范围:\([0, 2^{64})\)。
自然溢出:
unsigned int
自然溢出:对 \(2^{32}\) 取模。unsigned long long
自然溢出:对 \(2^{64}\) 取模。
输出格式:
unsigned int
输出格式:%u
。unsigned long long
输出格式:%llu
。
C++ STL
lower_bound & upper_bound
lower_bound(s, t, val)
:指向 \([s, t)\) 中第一个 \(\geq \mathrm{val}\) 的元素的迭代器,需要保证 \([s, t)\) 升序排序。upper_bound(s, t, val)
:指向 \([s, t)\) 中第一个 \(> \mathrm{val}\) 的元素的迭代器,需要保证 \([s, t)\) 升序排序。--upper_bound(s, t, val)
:指向 \([s, t)\) 中最后一个 \(\leq \mathrm{val}\) 的元素的迭代器,需要保证 \([s, t)\) 升序排序。
sort
sort()
的实现基于快速排序。
升序排序(默认):sort(s, t)
。
降序排序:sort(s, t, greater<int>())
。
stable_sort
stable_sort()
的实现基于归并排序。比较次数稳定且相较于std::sort
较少。
unique
unique()
去除容器中相邻的重复元素,并返回去重之后的尾迭代器(或指针)。常用于离散化。- 将一个
vector
去重:m = unique(a.begin(), a.end()) - a.begin()
。 - 将一个下标为 \(1\sim n\) 的数组去重:
m = unique(a + 1, a + 1 + n) - (a + 1)
。
next_permutation & prev_permutation
next_permutation()
求出指定部分的元素,构成的全排列中,字典序排在下一个的排列,并直接在序列上更新。特别地,若不存在排名更靠后的排列,则返回 false;否则返回 true。prev_permutation()
求出指定部分的元素,构成的全排列中,字典序排在上一个的排列,并直接在序列上更新。特别地,若不存在排名更靠前的排列,则返回 false;否则返回 true。- 遍历 \(1 \sim n\) 的 \(n!\) 种全排列:
for (int i = 1; i <= n; i ++) a[i] = i;
do {
// // Calculate the information of a
} while (next_permutation(a + 1, a + 1 + n));
vector
- vector 编号从 \(0\) 开始,前闭后开。
- vector 可以理解成一个变长数组,内部基于倍增思想。设 \(n, m\) 分别为 vector 的实际长度与最大长度。
- 插入元素前,若 \(n = m\),则在内存中申请 \(2m\) 的连续空间,并把内容转移到新的地址上,再执行插入。
- 删除元素后,若 \(n \leq m / 2\),则释放一半的空间。
vector<int> V(n)
:指定该容器大小为 \(n\)。vector<int> V(n, m)
:指定该容器大小为 \(n\),初始值均为 \(m\)。V.resize(n, m)
:重新制定该容器大小为 \(n\),若容器变长,则以值 \(m\) 填充;若容器变短,则超出限制的元素被删除。lower_bound(V.begin(), V.end(), val)
:指向容器中第一个 \(\geq \mathrm{val}\) 的元素的迭代器,需要保证容器升序排序。upper_bound(V.begin(), V.end(), val)
:指向容器中第一个 \(> \mathrm{val}\) 的元素的迭代器,需要保证容器升序排序。V.begin()
:指向容器中第一个元素的迭代器。V.end()
:指向容器中最后一个元素的下一个位置的迭代器。即--V.end()
指向容器中最后一个元素的迭代器。V.front()
:返回容器的第一个元素,等价于*V.begin()
和V[0]
。V.back()
:返回容器的最后一个元素,等价于*--V.end()
和V[V.size() - 1]
。
区间计数: upper_bound(V.begin(), V.end(), r) - lower_bound(V.begin(), V.end(), l)
。
priority_queue
大根堆(默认):priority_queue<int> q
。
小根堆:priority_queue< int, vector<int>, greater<int> > q
。
set
升序排序(默认):set<int> s
。
降序排序:set< int, greater<int> > s
。
- 设
it
是个迭代器,则it--
与it++
的时间复杂度均为 \(\mathcal{O}(\log n)\)。 s.lower_bound(val)
:指向容器中第一个 \(\geq \mathrm{val}\) 的元素的迭代器。s.upper_bound(val)
:指向容器中第一个 \(> \mathrm{val}\) 的元素的迭代器。- 在 set 中进行二分时,应执行
s.lower_bound(val)
;而非lower_bound(s.begin(), s.end(), val)
。 s.begin()
:指向容器中最小元素的迭代器。s.end()
:指向容器中最大元素的下一个位置的迭代器,即--s.end()
指向容器中最大元素的迭代器。
multiset
升序排序(默认):multiset<int> s
。
降序排序:multiset< int, greater<int> > s
。
s.count(val)
:返回所有数值为 \(\mathrm{val}\) 的元素个数。时间复杂度为 \(\mathcal{O}(k + \log n)\),其中 \(k\) 为 \(\mathrm{val}\) 的元素个数。s.erase(val)
:将所有数值为 \(\mathrm{val}\) 的元素删除,返回被删除的元素个数。时间复杂度为 \(\mathcal{O}(k + \log n)\),其中 \(k\) 为 \(\mathrm{val}\) 的元素个数。s.erase(pos)
:将迭代器 \(\mathrm{pos}\) 指向的元素删除。- 在 multiset 中删除一个数值为 \(\mathrm{val}\) 的元素,应执行
s.erase(s.find(val))
。 - 使用 multiset 维护多关键字的数据,在重载小于号时,应保证两个不同的元素能被有效区分。
map
-
map 的实现基于红黑树。因此 map 内部的所有元素都是有序的。
-
[]
运算符:h[key]
返回 \(\mathrm{key}\) 映射到的 \(\mathrm{value}\) 的引用,时间复杂度为 \(\mathcal{O}(\log n)\)。特别要注意的是,若查找的 \(\mathrm{key}\) 不存在,则执行h[key]
后,\(h\) 会自动新建一个二元组 \((\mathrm{key}, 0)\),并返回 \(0\) 的引用(这里的 \(0\) 表示一个广义零值)。该二元组白白地占用了空间,并且会计入h.size()
的大小中。故推荐在使用[]
运算符前,先使用find
检查一下 \(\mathrm{key}\) 的存在性。
unordered_map
- unordered_map 的实现基于 Hash。因此 unordered_map 不保证元素的顺序。
[]
运算符:同 map,但时间复杂度为常数级别。
bitset
- bitset 编号从 \(0\) 开始,前闭后开。
- bitset 可以进行各种二进制操作,如取反
~
、与&
、或|
、异或^
、左移<<
、右移>>
。 b.count()
:返回 \(1\) 的个数。b.any()
:查询 bitset 中是否存在 \(1\)。b.none()
:查询 bitset 中是否全 \(0\)。b.reset()
:将所有位改为 \(0\)。b.set()
:将所有位改为 \(1\)。b._Find_first()
:查询低位到高位第一个 \(1\) 的位置。b._Find_next()
:查询当前位置之后下一个 \(1\) 的位置。- 可以按照如下的方式,遍历一个 bitset:
for (int i = B._Find_first(); i != B.size(); i = B._Find_next(i))
// Calculate the information of i
随机数据生成与对拍
随机数据生成
生成一个区间 \([l, r]\) 中的随机整数:
std::mt19937
生成出的随机数的类型为unsigned int
。std::mt19937_64
生成出的随机数的类型为unsigned long long
。
std::mt19937_64 mt_rnd((unsigned)time(0));
int R(int l, int r) { // 生成一个区间 [l, r] 中的随机整数
return mt_rnd() % (r - l + 1) + l;
}
生成随机区间列:
std::mt19937_64 mt_rnd((unsigned)time(0));
int R(int l, int r) {
return mt_rnd() % (r - l + 1) + l;
}
int main() {
for (int i = 1; i <= m; i ++) {
int l = R(1, n), r = R(1, n);
if (l > r) std::swap(l, r);
printf("%d %d\n", l, r);
}
}
生成随机父向树:
std::mt19937_64 mt_rnd((unsigned)time(0));
int R(int l, int r) {
return mt_rnd() % (r - l + 1) + l;
}
int main() {
printf("%d\n", n);
for (int i = 2; i <= n; i ++)
printf("%d %d\n", R(1, i - 1), i);
}
生成随机树(prufer 序列):
std::mt19937_64 mt_rnd((unsigned)time(0));
int R(int l, int r) {
return mt_rnd() % (r - l + 1) + l;
}
int n;
int prufer[N];
int deg[N];
std::pair<int, int> e[N];
int main() {
for (int i = 1; i <= n - 2; i ++) prufer[i] = R(1, n);
for (int i = 1; i <= n; i ++) deg[i] = 1;
for (int i = 1; i <= n - 2; i ++) deg[prufer[i]] ++;
int leaf = 0, p = 0;
for (int i = 1; i <= n; i ++)
if (deg[i] == 1) { leaf = p = i; break; }
for (int i = 1; i <= n - 2; i ++) {
int x = prufer[i];
e[i] = std::make_pair(leaf, x);
if (-- deg[x] == 1 && x < p) {
leaf = x;
} else {
p ++;
while (deg[p] != 1) p ++;
leaf = p;
}
}
e[n - 1] = std::make_pair(leaf, n);
for (int i = 1; i < n; i ++)
printf("%d %d\n", e[i].first, e[i].second);
}
生成随机连通图(稀疏图):
- 先生成一棵 \(n - 1\) 条边的随机父向树,保证连通。再生成剩下的 \(m - n + 1\) 条边。
std::mt19937_64 mt_rnd((unsigned)time(0));
int R(int l, int r) {
return mt_rnd() % (r - l + 1) + l;
}
int n, m;
std::pair<int, int> e[M];
std::map< std::pair<int, int>, bool > exist;
int main() {
for (int i = 1; i < n; i ++) {
e[i] = std::make_pair(R(1, i), i + 1);
exist[e[i]] = 1;
}
for (int i = n; i <= m; i ++) {
int x, y;
do {
x = R(1, n), y = R(1, n);
if (x > y) std::swap(x, y);
} while (x == y || exist[std::make_pair(x, y)]);
e[i] = std::make_pair(x, y);
exist[e[i]] = 1;
}
for (int i = 1; i <= m; i ++)
printf("%d %d\n", e[i].first, e[i].second);
}
对拍
- Windows 系统命令
fc
(或类 Unix 系统命令diff
)可以比较两个文件是否一致,一致返回 \(0\),否则返回非零值。 clock()
可以返回当前程序已经运行的 CPU 时间,Windows 下单位 ms,Unix 下单位 s。
#include <bits/stdc++.h>
int main() {
for (int T = 1; T <= 10000; T ++) {
printf("Test #%d:\n", T);
system("gen.exe"); // 生成数据
system("std_1.exe"); // 运行程序 1
system("std_2.exe"); // 运行程序 2
if (system("fc data1.out data2.out")) {
puts("WA");
return 0;
} else {
puts("AC");
}
}
}
A - 基础算法 算法基础
I/O 优化
读入优化:
template <class &T>
inline void read(T &x) {
static char s;
static bool opt;
while (s = getchar(), (s < '0' || s > '9') && s != '-');
x = (opt = s == '-') ? 0 : s - '0';
while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
if (opt) x = ~x + 1;
}
快速乘
\(\mathcal{O}(\log n)\) 快速乘:
特别要注意的是,模数大小的两倍不能超过 long long
上界。
typedef long long s64;
s64 qmul(s64 a, s64 b, s64 p) {
s64 ans = 0;
for (; b; b >>= 1) {
if (b & 1) ans = (ans + a) % p;
a = 2 * a % p;
}
return ans;
}
\(\mathcal{O}(1)\) 快速乘:
注意到:
利用 long double
来处理 \(\left\lfloor \frac{a \times b}{p} \right\rfloor\)。
虽然 \(a \times b\) 和 \(\left\lfloor \frac{a \times b}{p} \right\rfloor \times p\) 的数值可能很大,但是两者的差一定在 \([0, p)\) 之间,我们只关心它们的前 64 位即可,这正好可以用 long long
运算的自然溢出来处理。
typedef long long s64;
s64 qmul(s64 a, s64 b, s64 p) {
s64 c = (long double)a * b / p + 1e-8;
s64 ans = a * b - c * p;
if (ans < 0) ans += p;
if (ans >= p) ans -= p;
return ans;
}
快速幂
int qpow(int a, int b, int p) {
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
}
return ans;
}
整数除法 上下取整
int normal_dn(int x, int y) {
return x / y;
}
int normal_up(int x, int y) {
return (x + y - 1) / y;
// return x % y == 0 ? x / y : x / y + 1;
}
int div_dn(int x, int y) {
bool flag = 0;
if (x < 0) x = -x, flag ^= 1;
if (y < 0) y = -y, flag ^= 1;
return flag ? -normal_up(x, y) : normal_dn(x, y);
}
int div_up(int x, int y) {
bool flag = 0;
if (x < 0) x = -x, flag ^= 1;
if (y < 0) y = -y, flag ^= 1;
return flag ? -normal_dn(x, y) : normal_up(x, y);
}
二分
>> 1
向下取整,/2
向零取整。- 在整数域上进行二分时:
- 若分成的区间为 \([l, \mathrm{mid}], [\mathrm{mid} + 1, r]\),
mid = (l + r) >> 1
。 - 若分成的区间为 \([l, \mathrm{mid} - 1], [\mathrm{mid}, r]\),
mid = (l + r + 1) >> 1
。
- 若分成的区间为 \([l, \mathrm{mid}], [\mathrm{mid} + 1, r]\),
- 在实数域上进行二分时,
mid = (l + r) / 2
。但实数域上的二分容易爆精度,此时可以考虑固定一个二分次数。
倍增
从大到小的倍增:从大到小枚举 \(2^i\) 倍增。
从小到大的倍增:记 \(p\) 表示上一次成功倍增段的末尾,\(len\) 表示当前倍增的步长(初始时为 \(1\))。具体流程为:
- 尝试加入区间 \([p, p + len]\)。
- 若加入区间 \([p, p + len]\) 合法,则 \(p \gets p + len\),\(len \gets len \times 2\)。
- 若加入区间 \([p, p + len]\) 不合法,则 \(len \gets len / 2\)。
- 重复上述流程,直到 \(len = 0\) 时,结束倍增。
二叉树
\(n_0\) 与 \(n_2\) 的关系:\(n_0 = n_2 + 1\)。
\(n_0\) 与 \(n_2\) 的关系证明:
贪心
贪心的常见证明方法:
- 微扰法(邻项交换)。
- 范围缩放法。
- 决策包容性。
- 反证法。
- 数学归纳法。
反悔贪心:待填。
排序
选择排序
- 每轮找出第 \(i\) 小的元素(即 \(a_i \sim a_n\) 中最小的元素),然后将其与数组的第 \(i\) 位交换。进行 \(n - 1\) 轮即可完成排序。
- 时间复杂度 \(\mathcal{O}(n^2)\),不稳定。
void selection_sort() {
for (int i = 1; i < n; i ++) {
int ith = i;
for (int j = i + 1; j <= n; j ++)
if (a[j] < a[ith]) ith = j;
std::swap(a[i], a[ith]);
}
}
冒泡排序
- 每轮检查相邻两个元素,若 \(a_i > a_{i + 1}\) 则交换 \(a_i, a_{i + 1}\)。经过 \(k\) 轮后,数组末尾的 \(k\) 个元素必然是最大的 \(k\) 个元素,故进行 \(n - 1\) 轮即可完成排序。
- 时间复杂度为 \(\mathcal{O}(n^2)\),稳定。
- 每轮冒泡排序,对逆序对的影响:记 \(c_i\) 表示以 \(i\) 结尾的逆序对个数,则 \(c_i \gets \max(0, c_{i + 1} - 1)\)。
- 每轮冒泡排序的本质:从第一个数开始考虑,将其放到右边第一个比它大的数之前。然后从这个比它大的元素开始考虑,以此类推。设每次考虑的数是 \(i\)(显然 \(c_i = 0\)),将其放到下标 \(j\) 之前的位置,则下标在 \(i + 1 \sim j - 1\) 中的数均向左移了一位,且逆序对个数减少了一个。
void bubble_sort() {
do {
bool flag = 0;
for (int i = 1; i < n; i ++)
if (a[i] > a[i + 1]) {
std::swap(a[i], a[i + 1]);
flag = 1;
}
} while (flag);
}
快速排序
- 通过分治的方式来将一个数组排序:
- 选取一个中间值 \(m\),将 \(\leq m\) 的数划分进左子数组,将 \(> m\) 的数划分进右子数组。
- 分别对左、右子数组递归进行快速排序。
- 平均时间复杂度 \(\mathcal{O}(n \log n)\),最坏时间复杂度 \(\mathcal{O}(n^2)\)。不稳定。
归并排序
- 通过分治的方式来将一个数组排序:
- 取 \(\mathrm{mid} = \left\lfloor \frac{l + r}{2} \right\rfloor\),将区间 \([l, r]\) 分成左区间 \([l, \mathrm{mid}]\) 与右区间 \([\mathrm{mid} + 1, r]\)。
- 分别对左、右子区间递归进行归并排序。
- 将左、右两个有序子区间,合并成一个大的有序区间。每次比较两个子区间的最小元素,将其中的较小者加入。
- 时间复杂度 \(\mathcal{O}(n \log n)\),稳定。
void merge_sort(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);
int i = l, j = mid + 1;
for (int id = l; id <= r; id ++)
if (i <= mid && (j > r || a[i] <= a[j]))
tmp[id] = a[i ++];
else
tmp[id] = a[j ++];
for (int id = l; id <= r; id ++)
a[id] = tmp[id];
}
高精度
- 使用结构体封装,从低位储存到高位。
- 高精 \(+\) 高精。对位相加,时间复杂度 \(\mathcal{O}(L)\)。
- 高精 \(-\) 高精。对位相减,时间复杂度 \(\mathcal{O}(L)\)。
- 高精 \(\times\) 低精。对位相乘,时间复杂度 \(\mathcal{O}(L)\)。
- 高精 \(\div\) 低精。模拟竖式长除法,使用低精度除法实现,时间复杂度 \(\mathcal{O}(L)\)。
- 高精 \(\times\) 高精。本质上即为 \(c_{i + j - 1}\gets_+ a_i\times b_j\),时间复杂度 \(\mathcal{O}(L^2)\)。可以使用 FFT 优化至 \(\mathcal{O}(L \log L)\)。
- 高精 \(\div\) 高精。模拟竖式长除法,使用高精度减法实现,时间复杂度 \(\mathcal{O}(L^2)\)。
struct bignum {
int len, num[SIZE];
bignum() {
len = 0;
memset(num, 0, sizeof(num));
}
void input() {
static char str[SIZE];
scanf("%s", str + 1);
len = strlen(str + 1);
for (int i = 1; i <= len; i ++) num[len - i + 1] = str[i] - '0';
}
void output() {
if (!len) {
puts("0");
} else {
for (int i = len; i >= 1; i --) printf("%d", num[i]);
puts("");
}
}
bool operator < (const bignum &b) const {
if (len ^ b.len) return len < b.len;
for (int i = len; i >= 1; i --)
if (num[i] ^ b.num[i]) return num[i] < b.num[i];
return 0;
}
bool operator <= (const bignum &b) const {
if (len ^ b.len) return len < b.len;
for (int i = len; i >= 1; i --)
if (num[i] ^ b.num[i]) return num[i] < b.num[i];
return 1;
}
bignum operator + (const bignum &b) const {
bignum c; c.len = std::max(len, b.len);
for (int i = 1; i <= c.len; i ++) c.num[i] = num[i] + b.num[i];
for (int i = 1; i <= c.len; i ++)
if (c.num[i] >= 10) c.num[i + 1] ++, c.num[i] -= 10;
while (c.num[c.len + 1]) {
c.len ++;
if (c.num[c.len] >= 10) c.num[c.len + 1] ++, c.num[c.len] -= 10;
}
return c;
}
bignum operator - (const bignum &b) const {
bignum c; c.len = len;
for (int i = 1; i <= c.len; i ++) c.num[i] = num[i] - b.num[i];
for (int i = 1; i <= c.len; i ++)
if (c.num[i] < 0) c.num[i + 1] --, c.num[i] += 10;
while (c.len && !c.num[c.len]) c.len --;
return c;
}
bignum operator * (const int &b) const {
bignum c; c.len = len;
for (int i = 1; i <= len; i ++) c.num[i] = num[i] * b;
for (int i = 1; i <= len; i ++) {
c.num[i + 1] += c.num[i] / 10;
c.num[i] %= 10;
}
while (c.num[c.len + 1]) {
c.len ++;
c.num[c.len + 1] += c.num[c.len] / 10;
c.num[c.len] %= 10;
}
return c;
}
std::pair<bignum, int> operator / (const int &b) const {
bignum c; c.len = len;
int r = 0;
for (int i = len; i >= 1; i --) {
c.num[i] = (r * 10 + num[i]) / b;
r = (r * 10 + num[i]) % b;
}
while (c.len && !c.num[c.len]) c.len --;
return std::make_pair(c, r); // c 是商,r 是余数
}
bignum operator * (const bignum &b) const {
bignum c; c.len = len + b.len;
for (int i = 1; i <= c.len; i ++) {
for (int j = 1; j <= i; j ++) c.num[i] += num[i - j + 1] * b.num[j];
c.num[i + 1] += c.num[i] / 10;
c.num[i] %= 10;
}
while (c.len && !c.num[c.len]) c.len --;
return c;
}
std::pair<bignum, bignum> operator / (const bignum &b) const {
bignum a = *this;
if (a < b) return std::make_pair(bignum(), a);
bignum c; c.len = len - b.len + 1;
for (int i = c.len; i >= 1; i --) {
bignum tmp; tmp.len = b.len + i - 1;
for (int j = 1; j <= b.len; j ++) tmp.num[j + i - 1] = b.num[j];
while (tmp <= a) a = a - tmp, c.num[i] ++;
}
while (c.len && !c.num[c.len]) c.len --;
return std::make_pair(c, a); // c 是商,a 是余数
}
};