所有相关大整数的操作本质就是模拟,模拟人工处理大整数的过程
单精度:能用一个内置类型存储的整数,unsigned long long数据类型能存储的最大数为2的64次幂-1
双精度:不能用内置类型存储的大整数,通常用数组存储每一个数位
为了方便模拟竖式计算,采用小端序(与书写顺序相反,低位在前,存储在地址的低位)存储双精度大整数 ,如2021的小端序存储:
加减法都是从地位开始运算,小端序地位处于地址的地位,循环枚举的时候符合正向枚举的习惯,而且位数的变化更加方便,容易实现数组伸缩。
乘法:一个n位的整数和一个m位的整数相乘,结果最多为n + m位的整数,最少为1位
练习题目1:
输入两个大整数ab,计算a - b的结果,其中数据保证0 < b < a < 10^500。
输入描述:一行,两个大整数ab,中间用空格隔开。
输出描述:一行一个整数,表示a-b的结果。
示例 1:
输入:10000 9990
输出:10
代码:
#include
using namespace std;
int a[1000010], b[1000010], c[1000010], lena, lenb, lenc, i;
char n[100010], n1[100010], n2[100010];
int main(){
scanf("%s", n1);
scanf("%s", n2);
if (strlen(n1) < strlen(n2) || (strlen(n1) == strlen(n2) && strcmp(n1, n2) < 0)) {
strcpy(n, n1);
strcpy(n1, n2);
strcpy(n2, n);
cout << "-";
}
lena = strlen(n1); lenb = strlen(n2);
for(i = 0; i <= lena - 1; i++) a[lena - i] = int(n1[i] - '0');
for(i = 0; i <= lenb - 1; i++) b[lenb - i] = int(n2[i] - '0');
i = 1;
while (i <= lena || i <= lenb) {
if (a[i] < b[i]) {
a[i] += 10;
a[i+1]--;
}
c[i] = a[i] - b[i];
i++;
}
lenc = i;
while ((c[lenc] == 0) && (lenc > 1)) lenc--;
for (i = lenc; i >= 1; i--) cout << c[i];
cout << endl;
return 0;
}
练习题目2:
求两数的积。说明/提示:每个数字不超过 10^2000 ,需用高精度。
输入描述:两行,两个整数。
输出描述:一行一个整数表示乘积。
示例 1:
输入:
123456789
987654321
输出:
121932631112635269
#include
#define N 11111
using namespace std;
int a_digits[N], b_digits[N];
int a_len, b_len;
int ans_digits[N], ans_len;
char str1[N], str2[N];
int main() {
cin >> str1;
// 获取高精度整数长度
int a_len = strlen(str1);
for (int i = 0; i < a_len; ++i)
// TODO 请补全下述代码
a_digits[i]=str1[a_len-1-i]-'0' ; // 将字符转换成数字,倒着存进数位数组
cin >> str2;
// 获取高精度整数长度
int b_len = strlen(str2);
for (int i = 0; i < b_len; ++i)
// TODO 请补全下述代码
b_digits[i]=str2[b_len-1-i]-'0'; // 将字符转换成数字,倒着存进数位数组
// 1. 数位操作
ans_len = a_len + b_len; // 初始化长度
for (int i = 0; i < ans_len; ++i)
ans_digits[i] = 0;
// 因为是不断累加的形式,所以要将范围内的元素初始化为0。
for (int i = 0; i < a_len; ++i)
for (int j = 0; j < b_len; ++j){
// TODO 请补全下述代码
//cout<
ans_digits[i+j]=a_digits[i]*b_digits[j]+ans_digits[i+j] ;}
//cout<
// ans的每一位更新都要使用累加的形式,这是因为对于ans的第k位,满足i + j == k的(i, j)很多,所以可能答案的第k位可能先后被更新很多次。
// 2. 统一进位
int k = 0;
for (int i = 0; i < ans_len; ++i) {
// TODO 请补全下述代码
ans_digits[i]+=k;
k=ans_digits[i]/10;
ans_digits[i]%=10;
//cout<
}
// 3. 维护长度
while (ans_len > 1 && ans_digits[ans_len - 1] == 0)
--ans_len;
// 4. 输出
for (int i = ans_len - 1; i >= 0; --i)
cout << ans_digits[i];
cout << endl;
return 0;
}
练习题目3:
高精度加法,相当于a+b problem,不用考虑负数.
输入描述:分两行输入。a, b ≤ 10^10000
输出描述:输出只有一行,代表a+b的值
示例 1:
输入:
1001 9099
输出:
10100
#include
#define N 11111
using namespace std;
int a_digits[N], b_digits[N];
int a_len, b_len;
int ans_digits[N], ans_len;
char str1[N], str2[N];
int main() {
cin >> str1;
// 获取高精度整数长度
int a_len = strlen(str1);
for (int i = 0; i < a_len; ++i)
// TODO 请补全下述代码
a_digits[i]=str1[a_len-1-i]-'0' ; // 将字符转换成数字,倒着存进数位数组
cin >> str2;
// 获取高精度整数长度
int b_len = strlen(str2);
for (int i = 0; i < b_len; ++i)
// TODO 请补全下述代码
b_digits[i]=str2[b_len-1-i]-'0' ; // 将字符转换成数字,倒着存进数位数组
ans_len = max(a_len, b_len); // 初始长度
int k = 0; // 记录进位的变量
for (int i = 0; i < ans_len; ++i) {
// 假设a_len > b_len,这里需要保证b[b_len]到b[a_len - 1]的位置都是0,否则可能会出错。
// TODO 请补全下述代码
ans_digits[i]=a_digits[i]+b_digits[i]+k ; // 相加计算
k=ans_digits[i]/10; // 更新进位
ans_digits[i] %= 10;
}
if (k)
// TODO 请补全下述代码
ans_digits[ans_len]=1 ;
ans_len++; // 最高位进位
// 3. 输出
// 按照打印顺序输出,从高位到低位。
for (int i = ans_len - 1; i >= 0; --i)
cout << ans_digits[i];
cout << endl;
return 0;
}
时间复杂度
例:在一台主频为2.1GHz的电脑上,如果一台计算机每个时钟周期可以完成1条指令,如果一个算法需要10{20}条指令,那么它的运行时间将是10{20}(2*109)\3600\24÷(2∗109)÷3600÷24,答案约等于 580000
空间复杂度
一个5000 * 5000的二维数组int a所耗内存为5000×5000×4÷1024÷1024≈95 MB。(一个int占4个字节,1M=1024K,1K=1024bite)
还要考虑具体实现难度和一些针对实际问题的评价指标
提高算法时间复杂度的指导思想:充分利用每一次计算结果(对计算机的劳动成果报以最大尊重),但同时会因为对中间计算结构的存储(以方便再次利用),会加大空间复杂度。
空间复杂度和时间复杂度通常会此消彼长,牺牲一个来成全另一个
利用计算机运算速度快、精确度高的特点,对要解决问题的所有可能情况,一个不漏地进行检验,从中找出符合要求的答案的方法
子集枚举:
- 比特串法:集合{1,2,3,4,5,6},000101表示子集{4,6}(0表示对应元素不包含在子集中,1则表示包含在子集中)
假设我们有个集合{1,2,3,…,n},输出所有满足集合中所有数求和是3的倍数的子集的个数。
#include
using namespace std;
int n;
int main() {
scanf("%d", &n); // 集合大小,也就是01比特串的长度
int tot = 1 << n; // 枚举数字代替01比特串,范围为0到2^n - 1,用十进制数代表二进制数,tot最小的n+1位二进制数对应的十进制数,n=3,tot=8
int ans = -1; // 消除空集
for (int num = 0; num < tot; ++num) { // 枚举每个代表01比特串的数字
//第一层循环循环2^n次
long long sum = 0;
for (int i = 0; i < n; ++i) // 枚举01比特串的每一位
//第二层循环循环n次
if ((num >> i) & 1) { // 检查第j位是否为1,注意这里是从0开始枚举
sum += (i + 1); // 如果该位是1,就把对应的数字加到求和的变量里
}
if (sum % 3 == 0) ++ans; // 如果满足题目要求(3的倍数),计入答案
}
printf("%d\n", ans);
}
一种用一维数组表达二维信息的方式:把列看成数组的下标,行看成数组里的值的话,如下图所示
对应数组:int a[10] = {0, 5, 7, 1, 4, 2, 8, 6, 3}; // 这里我们从1开始存储,0号无意义。
STL(Standard Template Library):标准模板库,分为算法(algorithm)、容器(container)和迭代器(iterator)三个部分,之所以叫做“模板库”,是因为在STL中几乎所有代码都是用模板类或者模板函数的方式实现的。
next_permutation:3个参数,分别代表头指针(重拍范围包含头指针所指元素),尾指针(重拍范围不包含尾指针所指元素)和比较函数(可选)。
会将数组重拍成更大的数组,并返回Ture或False,False代表传入数组已经是最大的了,但是仍然会把数组改变从最小的,如:
int a[10] = {4, 3, 2, 1};
if (next_permutation(a, a + 4)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
for (int i = 0; i < 4; ++i) cout << a[i] << ' ';
cout << endl;
输出结果:
No
1 2 3 4
可以利用这种办法重拍数组下标实现一维数组元素所有排列的遍历。
调用next_permutation函数一次的复杂度为O(n)。如果对于不同元素的排列,如果连续调用next_permutation函数的话,均摊复杂度为O(1)。