太久没有写这类题型,都快忘的差不多了,总结一下。
高精度
高精度,也叫大整数计算,是指处理大数字的数学计算方法。在一般的科学计算中,经常会遇到需要计算小数点后几百位甚至更多的数字,或者是几千亿几百亿、几千几万位的大数字。这类数字无法用基本类型进行存储,因此需要使用高精度算法。
高精度算法是用计算机对于超大数据的一种模拟加、减、乘、除、乘方、阶乘、开方等运算。对于非常庞大的数字无法在计算机中正常存储,于是,将这个数字拆开,拆成一位一位的,或者是四位四位的存储到一个数组中,用一个数组去表示一个数字,这样这个数字就被称为是高精度数。高精度算法就是能处理高精度数各种运算的算法。
加法(A+B Problem(高精))
题目描述
高精度加法,相当于 a+b problem,不用考虑负数。
输入格式
分两行输入。a,b≤10500。
输出格式
输出只有一行,代表 a+b 的值。
输入输出样例
输入 #1复制
1 1
输出 #1复制
2
输入 #2复制
1001 9099
输出 #2复制
10100
说明/提示
20%20% 的测试数据,00≤a,b≤109;
40%40% 的测试数据,0≤a,b≤1018。
代码加注释:
cpp
#include<stdio.h> // 引入标准输入输出库
#include<string.h> // 引入字符串处理库
#include<algorithm> // 引入算法库,用于使用max函数
using namespace std; // 使用标准命名空间
int main()
{
char s1[100], s2[100]; // 定义两个字符数组,用于存储输入的字符串
int a[100], b[100], c[100]; // 定义三个整数数组,用于存储转换后的数字以及相加的结果
scanf("%s", s1); // 从标准输入读取字符串s1
scanf("%s", s2); // 从标准输入读取字符串s2
memset(a, 0, sizeof(a)); // 将数组a初始化为0
memset(b, 0, sizeof(b)); // 将数组b初始化为0
memset(c, 0, sizeof(c)); // 将数组c初始化为0
int len1 = strlen(s1); // 获取字符串s1的长度
int len2 = strlen(s2); // 获取字符串s2的长度
// 将字符串s1逆序存入数组a中
for (int i = 0, j = len1 - 1; i < len1; i++, j--) {
a[i] = s1[j] - '0'; // 字符转换为数字,'0'的ASCII码为48,故用'0'而不是48
}
// 将字符串s2逆序存入数组b中
for (int i = 0, j = len2 - 1; i < len2; i++, j--) {
b[i] = s2[j] - '0'; // 同上,字符转换为数字
}
int len = max(len1, len2); // 取两个字符串长度的最大值作为相加后数组c的长度
// 相加操作
for (int i = 0; i < len; i++) {
c[i] += a[i] + b[i]; // 每一位相加
c[i + 1] += c[i] / 10; // 进位处理
c[i] %= 10; // 取个位数
}
int i = 99; // 从数组c的末尾开始
while (i > 0 && c[i] == 0) { // 去除前导0
i--;
}
// 打印结果
for (int j = i; j >= 0; j--) {
printf("%d", c[j]);
}
return 0; // 程序正常退出
}
减法(高精度减法)
题目描述
高精度减法。
输入格式
两个整数 a,b(第二个可能比第一个大)。
输出格式
结果(是负数要输出负号)。
输入输出样例
输入 #1复制
cpp2 1
输出 #1复制
cpp1
说明/提示
- 20% 数据 a,b 在 long long 范围内;
- 100% 数据 0<a,b≤1010086。
代码加注释:
cpp
#include<stdio.h> // 引入标准输入输出库
#include<string.h> // 引入字符串处理库
#include<iostream> // 引入C++标准输入输出库(这里用不到,可以去掉)
using namespace std; // 使用标准命名空间(因为包含了iostream,这里用到了)
int main()
{
char s1[100000], s2[100000]; // 定义两个字符数组,用于存储输入的字符串
int a[100000], b[100000], c[100000]; // 定义三个整数数组,用于存储转换后的数字以及相减的结果
scanf("%s", s1); // 从标准输入读取字符串s1
scanf("%s", s2); // 从标准输入读取字符串s2
memset(a, 0, sizeof(a)); // 将数组a初始化为0
memset(b, 0, sizeof(b)); // 将数组b初始化为0
memset(c, 0, sizeof(c)); // 将数组c初始化为0
int len1 = strlen(s1); // 获取字符串s1的长度
int len2 = strlen(s2); // 获取字符串s2的长度
// 将字符串s1逆序存入数组a中
for (int i = 0, j = len1 - 1; i < len1; i++, j--) {
a[i] = s1[j] - '0'; // 字符转换为数字,使用'0'的ASCII码值进行转换
}
// 将字符串s2逆序存入数组b中
for (int i = 0, j = len2 - 1; i < len2; i++, j--) {
b[i] = s2[j] - '0'; // 同上
}
int f = 0; // 用于标记哪个数更大,以便决定是相减还是相加(这里实际上是相减)
if (len2 > len1) f = 1; // 如果s2更长,则s2更大
else if (len2 == len1) {
for (int i = 0; i < len1; i++) {
if (s2[i] > s1[i]) {
f = 1;
break;
}
}
}
if (f == 1) { // 如果s2更大
for (int i = 0; i < len2; i++) {
c[i] += b[i] - a[i]; // 每一位相减
if (c[i] < 0) { // 如果结果为负数,则需要向高位借位
c[i] += 10; // 加上10,变成正数
b[i + 1]--; // 高位减1,表示借位
}
}
// 去除前导0并打印负数结果
int i = 99999;
while (i > 0 && c[i] == 0) {
i--;
}
printf("-"); // 打印负号
for (int j = i; j >= 0; j--) {
printf("%d", c[j]);
}
}
else { // 如果s1更大或两者相等(但此时f为0,说明s1更大)
for (int i = 0; i < len1; i++) {
c[i] += a[i] - b[i]; // 每一位相减
if (c[i] < 0) { // 如果结果为负数,则需要向高位借位
c[i] += 10; // 加上10,变成正数
a[i + 1]--; // 高位减1,表示借位
}
}
// 去除前导0并打印结果
int i = 99999;
while (i > 0 && c[i] == 0) {
i--;
}
for (int j = i; j >= 0; j--) {
printf("%d", c[j]);
}
}
return 0; // 程序正常退出
}
乘法(【模板】高精度乘法 | A*B Problem 升级版)
题目背景
本题数据已加强,请使用 FFT 或分治乘法。
题目描述
给你两个正整数 a,b,求 a×b。
输入格式
第一行一个正整数,表示 a;
第二行一个正整数,表示 b。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入 #1复制
cpp83517934 327830610
输出 #1复制
cpp27379735249159740
说明/提示
【数据范围】
1≤a,b≤101000000
可能需要一定程度的常数优化。
数据由 NaCly_Fish 重造
用FFT来解决问题我还没有学会暂时是写不动了。这里简单介绍一下概念。下面的代码主要是高精度乘法模板,没有使用FFT算法。
FFT介绍:
FFT,即快速傅里叶变换(Fast Fourier Transform),是一种高效的计算离散傅里叶变换(DFT)和其逆变换的算法。DFT是将信号从时间域变换到频率域的一种数学工具,广泛应用于信号处理、图像处理、数值分析和物理学等领域。然而,直接计算DFT的复杂度是O(n^2),对于大数据量来说效率很低。FFT通过利用系数W的周期性和对称性,将DFT中一部分重复的计算合并起来,使得算法复杂度降低到O(n log n),从而大大提高了计算效率。
FFT的基本思想是分治策略。它将一个较长的序列(通常是2的整数次幂个数据)的DFT分解成两个较短的序列的DFT,然后再将这两个较短序列的DFT合并成一个较长的序列的DFT。这个过程可以递归进行,直到分解到最短的序列(通常是长度为1或2的序列)为止。
FFT有多种实现方式,如库利-图基(Cooley-Tukey)FFT算法和基2 DIT-FFT算法等。这些算法在具体实现上有所不同,但基本思想都是利用分治策略来降低计算复杂度。
分治乘法介绍:
分治乘法是一种利用分治策略 来解决大整数乘法问题的算法。分治策略的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,然后合并这些解,就可以得到原问题的解。
在大整数乘法中,分治策略可以通过将大整数拆分为更小的整数部分,然后分别计算这些部分的乘积,最后再将结果合并,从而显著减少计算难度和步骤。这种策略可以有效降低大整数乘法所需的计算资源,并提高运算效率。
分治乘法的实现方式有多种,例如对称拆分策略、抽象模型等。此外,还可以结合其他算法技术,如加减运算技术、多位乘运算技术、快速傅里叶变换等,来进一步提升分治乘法的计算速度。
总的来说,分治乘法是一种高效解决大整数乘法问题的算法策略,它通过分解问题、分别求解子问题并合并结果的方式,显著降低了计算复杂度和资源消耗。
常数优化介绍:
常数优化是编程中一种常见的优化手段,它主要是通过改进算法或代码实现,使得算法的运行时间减少,但其时间复杂度不变 。这种优化可以在不改变算法基本结构的情况下,显著提高程序的性能。
常数优化的方法多种多样,包括但不限于以下几种:
- 读入优化:对于输入输出的优化,比如使用快读(fast read)代替标准的输入方式,可以显著提高程序的读入速度。
- 寄存器优化 :在循环中使用
register
关键字声明变量,这可以使得变量存储在寄存器中,而非内存中,从而加快访问速度。- 内联函数优化 :使用
inline
关键字将短小的函数定义为内联函数,可以减少函数调用的开销。- 判断语句优化 :三目运算符通常比
if-else
语句更快,因此可以适当地使用三目运算符来优化判断语句。- 赋值优化 :对于多组询问的题目,如果数据是静态的,那么无需在每次询问前都进行清空操作。此外,当需要赋值的数组较大且元素值较为集中时,使用
memset
通常比使用for
循环进行赋值更加高效。- 类型优化 :选择合适的数据类型可以影响运算的速度。例如,
long long
类型下的运算通常比int
类型慢一些,因此在不需要大整数运算的情况下,应优先使用int
类型。- 运算优化:使用位运算来替代一些复杂的算术运算,可以显著提高运算速度。特别是与2的次幂有关的操作,使用位运算通常更加高效。
此外,还有一些更为精致的常数优化方法,例如通过精妙地控制循环的边界或者搜索剪枝来减少不必要的计算,以及及时退出循环、简化布尔表达式、减少变量声明和拷贝等良好的编程习惯。
需要注意的是,虽然常数优化可以提高程序的性能,但过度优化可能会导致代码的可读性和可维护性下降。因此,在进行常数优化时,需要权衡性能提升和代码质量之间的关系。
这串代码是普通高精度题目的解法思路:
cpp
//试过了,这道题通过简单的高精度算不出来,会时间超限。
//下列代码是简单的普通解法,过不了。
#include<stdio.h>
#include<string.h>
// 定义两个字符串s1和s2来存储输入的大整数
char s1[1005], s2[1005];
// 定义三个整型数组a, b, c来存储处理过程中的中间结果
int a[1005], b[1005], c[1005];
int main()
{
// 初始化数组a, b, c为0
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
// 读取输入的两个大整数
scanf("%s%s", s1, s2);
// 获取两个大整数的长度
int len1 = strlen(s1);
int len2 = strlen(s2);
// 将s1逆序存储到数组a中,转换为整型数组
for (int i = 0, j = len1 - 1; i < len1; i++, j--) {
a[i] = s1[j] - '0';
}
// 将s2逆序存储到数组b中,转换为整型数组
for (int i = 0, j = len2 - 1; i < len2; i++, j--) {
b[i] = s2[j] - '0';
}
// 执行大整数乘法运算
for (int i = 0; i < len1; i++) {
for (int j = 0; j < len2; j++) {
// 计算乘积,并累加到数组c的对应位置
c[i + j] += a[i] * b[j];
// 处理进位
c[i + j + 1] += c[i + j] / 10;
c[i + j] %= 10;
}
}
// 找到数组c中的最高位非零元素的位置
int i = 1004;
while (i > 0 && c[i] == 0) {
i--;
}
// 输出最终结果
for (int j = i; j >= 0; j--) {
printf("%d", c[j]);
}
return 0;
}
除法(A/B Problem)
题目描述
输入两个整数 a,b,输出它们的商。
输入格式
两行,第一行是被除数,第二行是除数。
输出格式
一行,商的整数部分。
输入输出样例
输入 #1复制
10 2
输出 #1复制
5
说明/提示
0≤a≤105000,1≤b≤109。
代码加解析:
cpp
#include<stdio.h>
#include<string.h>
// 定义全局变量
int b = 0, a[100000], c[1000000];
char s1[100000];
int main() {
// 初始化数组a和c为0
memset(a, 0, sizeof(a));
memset(c, 0, sizeof(c));
// 从标准输入读取字符串s1
scanf("%s", s1);
// 从标准输入读取整数b
scanf("%d", &b);
// 计算字符串s1的长度
int len = strlen(s1);
// 将字符串s1中的字符转换为数字并存储到数组a中
for (int i = 0; i < len; i++) {
a[i] = s1[i] - '0';
}
// 用于存储中间结果的变量
long long ans = 0;
// 计算除法结果并存储到数组c中
for (int i = 0; i < len; i++) {
c[i] = (ans * 10 + a[i]) / b;
ans = (ans * 10 + a[i]) % b;
}
// 查找商数组c中第一个非零元素的索引
int i = 0;
while (i < len && c[i] == 0) {
i++;
}
// 输出商
if (i < len) { // 如果找到了非零元素
for (int j = i; j < len; j++) { // 从第一个非零元素开始输出,直到商的最后一位
printf("%d", c[j]);
}
} else {
// 如果整个商都是零
printf("0"); // 输出0
}
return 0;
}