Peter算法小课堂—高精度算法

什么是高精算法?

我们在计算的时候,很有可能遇到这种几百位乘几百位的题目(这出题人真的坑),这个时候long long就不行了,所以就得有传说中的“高精度算法”。

试试手:

忒简单了。。。CSP-J1 2023年第7题

7.以下关于高精度算法的说法错误的是()

A. 高精度计算主要是用来处理大整数或需要保留多位小数的运算。

B. 大整数除以小整数的处理的步骤可以是,将被除数和除数对齐,从左到右逐位尝试将除数乘以某个数,通过减法得到新的被除数,并累加商。

C. 高精度乘法的运算时间只与参与运算的两个整数中长度较长者的位数有关。

D. 高精度加法运算的关键在于逐位相加并处理进位。

答案:C

高精详细讲解

高精加

在读小学时,我们做加法都采用竖式方法 . 这样我们方便写出两个整数相加的算法 .

代码:

#include <cstdio>
#include <cstring>
 
using namespace std;
 
int main() {
    char a1[5005], b1[5005]; //用字符存储数字
    int a[5005], b[5005], c[5005]; //c[i] 用来储存每位相加的结果
    int len_a, len_b, len_c = 1, x, i;
 
    memset(a, 0, sizeof(a));
    memset(b, 0, sizeof(b));
    memset(c, 0, sizeof(c));
 
    scanf("%s%s", a1, b1); //输入两个加数
 
    len_a = strlen(a1);
    len_b = strlen(b1);
 
    for(i=0; i<len_a; i++) a[len_a - i] = a1[i] - '0'; // 将加数放进a数组
    for(i=0; i<len_b; i++) b[len_b - i] = b1[i] - '0'; // 将另一个加数放进b数组
 
    x = 0; // x为进位
    while(len_c <= len_a || len_c <= len_b) {
        c[len_c] = a[len_c] + b[len_c] + x; // 两数相加,再加上前两个数进位的
        x = c[len_c] / 10; // 刷新进位
        c[len_c] %= 10; // 进位后剩下的
        len_c++; //位数加1
    }
    c[len_c] = x;
    if(c[len_c] == 0) { //判断首位是否为0
        len_c--; // 不输出此位
    }
 
    for(int i=len_c; i>=1; i--) {
        printf("%d", c[i]); //输出每一位的数
    }
 
    return 0;
}

高精减

类似加法,同样使用竖式。在做减法运算时,需要注意的是:需要有借位处理。

代码:

#include <iostream>
#include <cstring>
 
int main() {
    int a[5005], b[5005], c[5005];
    int lena, lenb, lenc, i;
    char n[5005], n1[5005], n2[5005];
 
    std::memset(a, 0, sizeof(a));
    std::memset(b, 0, sizeof(b));
    std::memset(c, 0, sizeof(c));
 
    std::cin >> n1 >> n2; //输入被减数和减数
 
    lena = std::strlen(n1);
    lenb = std::strlen(n2);
 
    for(i=0; i<lena; i++) a[lena - i] = (int)n1[i] - '0';
    for(i=0; i<lenb; i++) b[lenb - i] = (int)n2[i] - '0'; //逆序存放排列
 
    i = 1;
    while(i <= lena || i <= lenb) {
        if(a[i] < b[i]) {
            c[i] = a[i] + 10 - b[i];
            a[i+1]--; //借位处理
        }
        else {
            c[i] = a[i] - b[i]; 
        }
        i++;
    }
 
    lenc = i;
    while(c[lenc] == 0 && lenc > 1) { //如果最后一位是0,是需要输出的
        lenc--;   // 不输出首位0
    }
 
    for(i=lenc; i>=1; i--) std::cout << c[i];
 
    return 0;
}

高精乘

类似加法,使用竖式。在做乘法时,同样也有进位。

分析 c 数组的下标变化规律,可以写出以下关系式:          c_{i} = c_{i'} + c_{i''} + ...

由此可见,c_{i} 跟 a[i] \times b[j] 乘积有关,跟上次的进位有关,跟还原 c_{i} 的值有关,分析下标规律,有 :

                                        c[i + j - 1] = a[i] + b[j] + x + c[i + j - 1]

代码:

#include <iostream>
#include <cstring>
 
int main() {
	int a[105], b[105], c[10005];
	char n1[105], n2[105], lena, lenb, lenc, j, i, x;
 
	std::memset(a, 0, sizeof(a));
	std::memset(b, 0, sizeof(b));
	std::memset(c, 0, sizeof(c));
	
	std::cin >> n1 >> n2;
	
	lena = std::strlen(n1);
	lenb = std::strlen(n2);
	
	for(i=0; i<=lena-1; i++) a[lena - i] = n1[i] - 48; 
	for(i=0; i<=lenb-1; i++) b[lenb - i] = n2[i] - 48; // 倒序储存
	
	for(i=1; i<=lena; i++) {
		x = 0;
		for(j=1; j<=lenb; j++) {
			c[i + j - 1] = c[i + j - 1] + x + a[i] * b[j];
			x = c[i + j - 1] / 10; // 进位
			c[i + j - 1] %= 10; // 剩余
		}
		c[i + lenb] = x; // 进位的数
	}
	
	lenc = lena + lenb;
	while(c[lenc] == 0 && lenc > 1) {
		lenc--; // 删除前导0
	}
	
	for(i=lenc; i>=1; i--) {
		std::cout << c[i];
	}  // 输出每一位
	   
    std::cout << std::endl;
	
	return 0;
} 

 高精除

1)高精除以低精

做除法时,每一次的商值都在 0~9 之间,每次求得的余数连接以后的若干位得到新的被除数,继续做除法。因此,在做高精度除法时,要涉及到乘法运算和减法运算,还有移位处理。当然,为了程序简洁,可以避免高精度乘法,用 0~9 次循环减法取代得到商的值。采用按位相除法

代码:

#include <iostream>
 
int main(){
    char n1[100];
    int a[100], c[100], lena, i, x = 0, lenc, b;
    
    std::memset(a, 0, sizeof(a));
    std::memset(c, 0, sizeof(c));
    
    std::cin >> n1 >> b;  
    lena = strlen(n1);
 
    for(i=1; i<=lena; i++) {
        a[i] = n1[i - 1] - '0'; //除法不需要逆序存放
    }
 
//-------------------------初始化------------------------------
 
    for(i=1; i<=lena; i++) {
        c[i] = (a[i] + x * 10) / b;  // 算上上一位剩下的继续除
        x = (a[i] + 10 * x) % b; // 求余
    }
 
    lenc = 1;
    while(c[lenc] == 0 && lenc < lena) {
        lenc++;
    }
 
    for(i=lenc; i<lena; i++) std::cout << c[i];
 
    return 0;
}

2)高精除以高精

高精除以低精是对被除数的每一位(这里的"一位"包含前面的余数,以下都是如此)都除以除数,而高精除以高精则使用减法模拟除法,对被除数的每一位都减去除数,一直减到当前位置的数(包含前面的余数)小于除数(由于每一位的数字小于10,所以对每一位最多进行10次运算)

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
 
using namespace std;
 
int a[50005], b[50005], c[50005], d;
 
void init(int a[]) {
	char s[50005];
	cin >> s;
	a[0] = strlen(s);		// 字符串存储,表示位数	
	for (int i=1; i<=a[0]; i++) {
		a[i] = s[a[0]-i] - 48;	// 正序储存
	} 	
}
 
void print(int a[]) {			
	if (a[0] == 0) {
		cout << 0 << endl;
		return;  // 位数为0,输出0
	}
	for (int i=a[0]; i>=1; i--) {
		cout << a[i];  // 输出函数
	}
	cout << endl;
	return;
} 
 
int compare(int a[], int b[]) {	
	if (a[0] > b[0]) {
		return 1; // 被减数大于减数
	} 
	if (a[0] < b[0]) {
		return -1; // 被减数小于减数
	}
	for (int i=a[0]; i>=1; i--) {	
		if (a[i] > b[i]) {
			return 1;
		} 
		if (a[i] < b[i]) {
			return -1;
		}   // 位数相同,找到第一位不同的进行比较
	} 
	return 0;					
}
 
void numcpy(int p[], int q[], int det) {
	for (int i=1; i<=p[0]; i++) {
		q[i+det-1] = p[i]; //复制p数组到q数组从det开始的地方
	}
	q[0] = p[0] + det - 1;
}
 
void jian(int a[], int b[]) {		
	int flag = compare(a, b);		 
	if (flag == 0)  {					
		a[0] = 0;
		return;
	}
	if (flag == 1) {				
		for (int i=1; i<=a[0]; i++) {
			if (a[i] < b[i]) {			 
				a[i+1]--;			
				a[i] += 10;
			}
			a[i] -= b[i];
		}
		while (a[0]>0 && a[a[0]]==0) {
			a[0]--;					
		} 
		return; 
	}				 
}  // 高精减法
 
void chugao(int a[], int b[], int c[]) {
	int tmp[50005];
	c[0] = a[0] - b[0] + 1;
	for (int i=c[0]; i>0; i--) {
		memset(tmp, 0, sizeof(tmp));	
		numcpy(b, tmp, i);// 清零
		while (compare(a, tmp) >= 0) {
			c[i]++;
			jian(a, tmp);	// 用减法模拟		
		} 
	}
	while (c[0] > 0 && c[c[0]] == 0) {
		c[0]--;
	}
	return;
}
 
int main() {
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	memset(c, 0, sizeof(c));
	
	init(a);
	init(b);
	chugao(a,b,c);
	print(c);	
	
	return 0;
}

一圈三连,必有回关。你的关注,就是我创作的动力。(~ ̄▽ ̄)~

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值