基础算法--高精度数据(1)

高精度数据处理一般内容简单,写代码难度较大,可能部分内容涉及基础数学、初等数论等知识。请小心食用。不过本节不会给大家太难的高精度处理,我们第一次接触,不能劝退大家对吧。

高精度算法是指,利用基础或高级的数学计算原理,来解决题目给出的数据量超出计算机正常存储范围的数据的一个方式。

开胃小菜-乘法

那么我们先来写个题练一下吧:信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

一本通1307-高精度乘法:
题目描述:

输入两个高精度正整数M和N(M和N均小于100位)。求这两个高精度数的积。

输入:

输入两个高精度正整数M和N

输出:

求这两个高精度数的积

大眼一看这不就是输出a*b嘛,但是M和N最大都是100位的数啊,怎么存的下1e100啊。更何况,两个100位数相乘得到的10000位数呢。(1位数相乘最大得到9*9=81,两位数;2位数相乘最大得到99*99=9801,四位数......)

那我们不妨想一下,乘法到底是怎么算的,假设两数为a和b(a的位数>=b的位数),那么取b的个位,乘a的所有位数,以后每次都向左偏移一位,依次取完b的位数个数字,结果相加。(我表达的可能不是很好,但大家都能回想起来惩罚的运算规则了,这就足够了)。给大家个图看一下吧:

编码时,我们选择数组不就行了吗,模拟位数,100位,10000位的位数还是蛮充足的。

下面大家自己写一下代码,我也去写一下,别偷看哦,动手时的你脑子想的更多,远比单纯动脑思考有用。

第一步--逻辑清晰,层次分明

首先我们将主函数的架构搭好,我们的逻辑是怎么样的:

在主函数外,我们还有一些维护数位的数组,中间临时数组,结果数组。并合理分配空间。并设置全局变量参与运算的数据的位数,避免了传参的麻烦。设置结果的位数变量ans_size。

#include <bits/stdc++.h>
using namespace std;

int a[100 + 10];//乘数
int b[100 + 10];//被乘数
int t[100 + 10];//中间数
int ans[10000 + 10];//结果
int ans_size;//ans最终的位数
int len_a, len_b;//分别是a的位数和b的位数

int main() {
	init();//初始化a、b数组数据
	for (int i = 1; i <= len_b; i++) {
		ans_size = calculate(b[i], i);
        //依次从b中取一位-b[i],并传入第几位-i
        //每次更新最终答案位数
	}
	Output(ans_size);
    //从第ans_size位依次往前输出,知道第1位输出完毕,构成的一串字符就是计算的结果
	return 0;
}

 第二步--根据题意,输入数据

输入的数据位数太多,整型家族的数据都存不下,那我们不妨存在字符串中,字符串存100个字符还不是轻而易举。同时我们保证输入的第一个数据位数大于等于第二个(这是我们参与数学运算时,习惯将长的数a放在上面,短的数b在下面,依次取位b[i]),而且,为了保证数据的高低位,我们将字符串逆置,这样低位在前,高位在后,虽然不符合我们的书写逻辑,但符合我们的运算逻辑。同时不要忘了将数字字符转化为数字时需要减去‘0’(根据ASCII码值进行运算转换)。

void init() {
	string st1, st2;
	cin >> st1 >> st2;
	if (st2.size() > st1.size())swap(st1, st2);//保证数a位数多,保留数学运算习惯
	reverse(st1.begin(), st1.end());//类似于栈的出入
	reverse(st2.begin(), st2.end());
	len_a = st1.size(), len_b = st2.size();
	for (int i = 0; i < len_a; i++) {
		a[i + 1] = st1[i] - '0';
	}
	for (int i = 0; i < len_b; i++) {
		b[i + 1] = st2[i] - '0';
	}
}

第三步--输出简单,提前安排

输出时,我们只需要传入一个参数(结果的位数),不传也可以,直接使用全局变量。

然后为了保证我们的结果位数准确,我们可能会在正确位数前多置零一位,此时我们需要判断,这是几位数,如果是一位数,那是0就是0,不用管,如果大于一位数,那前面的零我就不要了,也就是数组有效位数减一(我们使用while更加保险,if一次也是完全可以的),然后依次输出即可。输出这没什么说的了吧。重要的核心代码还是计算。

void Output(int n) {
	//输出第一位可能为0,最终未产生多余进位
	//if(n > 1 && 0 == ans[n]) n--;
	while (n > 1 && 0 == ans[n]) n--;

	for (int i = n; i > 0; i--) {
		cout << ans[i];
	}cout << endl;
}

第四步--核心代码,返璞归真 

首先将这是b的第几位暂时存下来,因为我们的每一位与乘数的结果临时储存在t中,我们需要从这第idx位开始往ans中放。

给你画个图你理解一下计算的过程:第一位9乘99999,我们的结果从第一位开始放在ans中,第二位9乘99999,我们的结果不放在个位了,从十位开始放。......依次类推,所以第几位b[i]乘a还是有必要存下来的。

存下来之后我们开始第一步:计算中间结果:(x为传进来的b的某一位)

本位结果t[i]=x*a[i]+上一位进位t[i-1]/10

上一位结果t[i-1]更新为t[i-1]%=10。(因为我们是十进制运算)

当x与a所有位上的数相乘完之后,我们不确定最高位存储的数据是不是一位数,(有可能是两位数),我们需要额外多维护一位,

第二步,将中间结果加到最终结果中:看上文,我们从第几位开始加?对的,第idx位(传的是b的第几位数,我就从第几位加)

while循环是为了将临时结果全部加到最终结果中

下面不加if判断也行,但是加上更容易我们理解。

这次不需要乘,只需要我们相加,然后满足十进制,跟上一步的思路差不多

最后也需要多维护一位。并返回最高位的数字。

int calculate(int x, int idx) {
	int ans_start_pos = idx;//答案从第几位开始加
	for (int i = 1; i <= len_a; i++) {//依次进位
		t[idx] = (x * a[i] + t[idx - 1] / 10);
		t[idx - 1] %= 10;
		idx++;
	}
	/*中间值最终进位*/
	t[idx] = t[idx - 1] / 10;
	t[idx - 1] %= 10;

	int cur_pos = ans_start_pos;//就是初始的idx
	while (cur_pos <= idx) {
		if (ans[cur_pos - 1] > 9) {
			ans[cur_pos] += ans[cur_pos - 1] / 10;
			ans[cur_pos - 1] %= 10;
		}
		ans[cur_pos] += t[cur_pos];
		cur_pos++;
	}
	/*结果最终进位*/
	ans[cur_pos] = ans[cur_pos - 1] / 10;
	ans[cur_pos - 1] %= 10;

	return cur_pos;
}

参考答案--乘法

总的代码如下:(有一些赘余的变量,但无伤大雅,考虑一下将中间结果数组去掉,只保留结果数组能不能运算?指定是能的,作为你学会本题思路后的额外思考)

#include <bits/stdc++.h>
using namespace std;

int a[100 + 10];//乘数
int b[100 + 10];//被乘数
int t[100 + 10];//中间数
int ans[10000 + 10];//结果
int ans_size;//ans最终的位数
int len_a, len_b;//分别是a的位数和b的位数
void init() {
	string st1, st2;
	cin >> st1 >> st2;
	if (st2.size() > st1.size())swap(st1, st2);//保证数a位数多,保留数学运算习惯
	reverse(st1.begin(), st1.end());//类似于栈的出入
	reverse(st2.begin(), st2.end());
	len_a = st1.size(), len_b = st2.size();
	for (int i = 0; i < len_a; i++) {
		a[i + 1] = st1[i] - '0';
	}
	for (int i = 0; i < len_b; i++) {
		b[i + 1] = st2[i] - '0';
	}
}
int calculate(int x,int idx) {
	int ans_start_pos = idx;//答案从第几位开始加
	for (int i = 1; i <= len_a; i++) {//依次进位
		t[idx] = (x * a[i] + t[idx - 1] / 10);
		t[idx - 1] %= 10;
		idx++;
	}
	/*中间值最终进位*/
	t[idx] = t[idx - 1] / 10;
	t[idx - 1] %= 10;

	int cur_pos = ans_start_pos;
	while (cur_pos <= idx) {
		if (ans[cur_pos - 1] > 9) {
			ans[cur_pos] += ans[cur_pos - 1] / 10;
			ans[cur_pos - 1] %= 10;
		}
		ans[cur_pos] += t[cur_pos];
		cur_pos++;
	}
	/*结果最终进位*/
	ans[cur_pos] = ans[cur_pos - 1] / 10;
	ans[cur_pos - 1] %= 10;

	return cur_pos;
}
void Output(int n) {
	//输出第一位可能为0,最终未产生多余进位
	//if(n > 1 && 0 == ans[n]) n--;
	while (n > 1 && 0 == ans[n]) n--;
	
	for (int i = n; i > 0; i--) {
		 cout << ans[i];
	}cout << endl;
}
int main() {
	init();//初始化a、b数据
	for (int i = 1; i <= len_b; i++) {
		ans_size=calculate(b[i],i);//依次从b中取一位b[i],第几位i
	}
	Output(ans_size);
	return 0;
}

 提交结果:AC,通过了。


好不容易哦,这才一道简单的乘法,就这么难理解了?但你从中学到的不仅仅只有这一点。例如:你知道了用数组元素存储数位;你知道了朴实无华的运算规律/方法;你知道了完成一道题的分析步骤;你初步知道如何处理高精度数据了......只要你认真看完了,思考了,学会了,你收获的远不止这些。哪里不懂,尽管留言,我看到就回复你。


本节主食--阶乘 

 题目:信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

阶乘我们也设计一点递推的思想,但没学过也没关系,我给你公式:n!=n * (n-1)!

还是按着上面的步骤来:

第一步--构建主函数,明确操作

由于这次我们是阶乘,产生的位数比较多,也不好确定上限,我们选择采用STL容器vecor来维护,因为递推的关系我们不仅需要维护结果数组,还要维护递推中,结果的前一个数组。那么我就可以这样写主函数了:

初始化数据--额外进行一个剪枝判断--进行核心操作--输出数据

int n;
vector<int> tmp(1);//提前开一个空间方便使用
vector<int> ans(1);
int main() {
	init();
    if (n <= 1) {
		cout << 1 << endl;
		return 0;
	}
	for (int i = 2; i <= n; i++) {
		getN(i);
	}
    output();
	return 0;
}

第二步--进行初始化,输入数据

本题也就输入一个数n,求它的阶乘,这个数还是10000以内的数,int完全能存的下,不用进行什么特殊的处理。tmp暂时为0的阶乘,ans暂时存储1阶乘

void init(){
    cin >> n;
    tmp[0]=1;
    ans[0]=1;
}

第三步--输出需判断,小心为上

注释写道代码里面了,仔细琢磨

void output(){
	Long len = ans.size()-1;
	bool flag = false;//当遇见第一个非零数时,改为真(目的)
	for (Long i = len; i >= 0; --i) {//倒着输出是因为下标小的位置是低位,我们要的是先输出高位
		if (!flag&&v[1][i] != 0)flag = true;//第一次遇见非零时,改为真(操作)
		if(flag) cout << v[1][i];//如果flag遇见第一个非零数改为真了,那之后就依次输出每一位
	}cout << endl;
}

第四步--最主要代码,核心计算

void getN(int idx) {
	tmp = vector<int>{0};//刷新上上一个阶乘,用来存放新的阶乘
	int length = ans.size();//获取上一个阶乘的长度
	int out = 0;//进位
	int mod = 0;//余数
	for (int i = 0; i < length; i++) {
		mod = (idx * ans[i] + out) % 10;
		out = (idx * ans[i] + out) / 10;
		tmp[i] = mod;
		tmp.push_back(out);//后面的进位需要自己拓展,无法访问下标
	}
	Long size = tmp.size() - 1;
	while (tmp[size] > 9) {
		tmp.push_back(tmp[size] / 10);
		tmp[size] %= 10;

		size++;
	}
	swap(tmp, ans);//保证了ans永远是上一次的阶乘
}

参考答案--阶乘

这里我把两个数组放在了一个数组中,你也可以把上述的代码合到一起。

#include <bits/stdc++.h>
using namespace std;
#define Long long long

/*10000内n的阶乘*/
void getN(vector<vector<int>>& v, int idx) {//两个一维数组v[0],v[1]分别是一个一维数组
	v[0] = vector<int>{0};//刷新上上一个阶乘,用来存放新的阶乘
	int length = v[1].size();
	int out = 0;
	int mod = 0;
	for (int i = 0; i < length; i++) {
		mod = (idx * v[1][i] + out) % 10;
		out = (idx * v[1][i] + out) / 10;
		v[0][i] = mod;
		v[0].push_back(out);
	}
	Long size = v[0].size() - 1;
	while (v[0][size] > 9) {
		v[0].push_back(v[0][size] / 10);
		v[0][size] %= 10;

		size++;
	}
	swap(v[0], v[1]);//保证了v[1]永远是上一次的阶乘
}
int main() {
	int n; cin >> n;
	vector<vector<int>> v(2,vector<int>(1));
	v[0][0]=1; v[1][0]=1;
	if (n <= 1) {
		cout << 1 << endl;
		return 0;
	}
	for (int i = 2; i <= n; i++) {
		getN(v, i);
	}


	Long len = v[1].size()-1;
	bool flag = false;
	for (Long i = len; i >= 0; --i) {
		if (!flag&&v[1][i] != 0)flag = true;
		if(flag) cout << v[1][i];
	}cout << endl;
	return 0;
}

 


感谢大家!

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值