介绍引入
想必大家都遇到过数字大小超过int类型范围的情况,是不是碰到int放不下的数字自然就会想到,long int,long long int,甚至unsigned long long int呢。但是有一些出题者就很可恶,偏要出一些连unsigned long long int也放不下的算法题,应对这些问题时,我们是否有相应的应对方法呢?答案是:当然有,高精度就是一种应对这种问题时很好的解决办法。相信你在看完这篇文章后能对高精度有着更深更好的理解。我会循序渐进的来讲,这样大家也更容易听懂。
引入题洛谷1009题
如下图:
大家是不是很容易想到一种解法 ,就是运用循环把一个个阶乘算出来并相加,于是我就写了如下代码
int main() { unsigned long long int s = 0, m = 1, n;//n为阶乘个数 scanf("%lld", &n); for (int i = 1; i <= n; i++) { m = 1; for (int j = 1; j <= i; j++) { m *= j; } s += m; //s为各个阶乘之和 } printf("%lld", s); return 0; }
似乎这题看起来也没什么难度嘛,但是,当你提交代码至洛谷判题系统后,便会出现报错
那么是什么原因导致的这个问题呢?便是我刚才说的,50的阶乘太过巨大,导致系统提供的类型放不下这么大的数了 。那么现在就该高精度出马了。
在讲这个高精度之前,我还想分享一道题,有助于对往后高精度的理解
高精度思想引入(洛谷1015)
下面来看看洛谷1015题回文数
其实我放这道题的目的主要是想引入进制转换,我将这个问题的题解放到下面,配上了注释,大家可以尝试看看,在看懂之后大概就学会2-16的进制转换了
#include <stdio.h> #include <string.h> int l, ret = 0; void add(char* m,char* d,int n) //数字相倒再相加的实现 { for (int i = 0; i < l; i++) d[l - i - 1] = m[i]; l += 2; for (int i=0; i <= l; i++) { m[i] += d[i]; if (m[i] >= n) m[i] -= n, m[i + 1]++; } while (!m[l - 1])l--; } int pdhws(char* m,int n) //判断是否为回文数 { for (int i = 0; i < l; i++) if (m[i] != m[l - i - 1])return 1; return 0; } int main() { char m[302] = { 0 };//数组char类型,可放字符,就是可以用字符表示的16进制,同时2-10进制也可以表示 char d[302] = { 0 };//将数字位数转化成数组元素形式储存 int n; scanf("%d%s", &n, m); l = strlen(m); //char类性可以用strlen计算字符串长度,也就就是位数 for (int i = 0; i < l; i++) { //将数组元素化成位数放入 if (m[i] >= '0' && m[i] <= '9')m[i] = m[i] - '0';//这里的数字转化非常妙可以看看 else m[i] = m[i] - 'A' + 10; } while (pdhws(m,n)) { ret++; if (ret > 30) { printf("Impossible!"); return 0; } add(m,d,n); } printf("STEP=%d", ret); return 0; }
这里进制转化实现的主要方式就是将各个进制的每一个位数分别放到数组的元素中, 然后从小到大依次运算进位,实现对不同进制的操作和运算。
就比如说数组相加后十进制数组{12,15,6}设6为百位,15为十位,12为各位,在一遍运算后,12-10=2,15进一位变16,16-10=6,6进一位变7,最后数组变成{2,6,7}从右往左刚好就是十进制形式。别的进制也是同样的道理。
也许你会问,这些数字都是分开的,怎么输出七百六十二(762)呢?难道要7*100+6*10+2吗?不错,这确实是一种解决方法,但是duck不必。经过我的研究,其实并不需要一次性用%d输出762,在洛谷的判题系统中,它只管你输出的格式是不是762,并不会在意你是百位输出,还是一个个数字输出,你完全可以用for循环把7-6-2三个数字依次打出来。这也为后面高精度的成功运行打下了基础。
说了这么多,想必大家已经对进制的转化运用和计算有了理解,接下来的高精度也就好理解了。
回到1009题并运用高精度
来来来,又回到了我们最开始的问题,嘿嘿。既然已经知道了如何用数组存放不同进制的位数以达到进制转化和运算,那我们便开启了一种新的存储数字的方式,用数组存储。可能你要问用C所给类型存储不就完了,你这用数组不是多此一举吗?
但是,你想想,数组中一个元素是一位,数组又可以存多少个元素呢?如果我想,我完全可以将数组中存储1000甚至100000个元素,也就相当于可以存下100000位大小的超级大数字,是类型unsigned long long int远远无法比拟的,是不是变的有意思起来了呢,我们完全可以用高精度存取50!这样的大数据。
有了以上思考,下面的代码,便是本题的一个正确且AC的正解,我在旁边也加了注释,方便理解
//高精度算法真是太好玩辣 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int ws[101] = { 0 };//存放各个位数的数组,此数组存放各个阶乘 int collect[101] = { 0 };//题目要求阶乘之和,此数组存放各个阶乘之和 ws[0] = 1;//阶乘起始数字为1 collect[0] = 1; int i, j, n; scanf("%d", &n); for (i = 2; i <= n; i++) { for (j = 0; j < 100;j++)ws[j] *= i;//高精度的一种乘法运算,在乘一个数字时,每个数字都需要乘一遍 for (j = 0; j < 100; j++) //将相乘之后位数超过9的数字进位,得到新的高精度数字 if (ws[j] > 9) { ws[j + 1] += ws[j] / 10; ws[j] %= 10; } for (j = 0; j < 100; j++) {//将得到的新数加到高精数collect中 collect[j] += ws[j]; if (collect[j] > 9) { collect[j + 1] += collect[j] / 10; collect[j] %= 10; } } } for (j = 100; j >= 0;j--)//输出所得的结果,一位一位把位数输出 if(collect[j]>0) do { printf("%d", collect[j]); j--; } while (j >= 0); return 0; }
以上代码涉及了高精度数字的储存,乘法,加法,看完后,是不是对高精度有了更深的理解呢?
高精度的代码块,创造super_int
讲到这里,基本上已经讲完了高精度的储存和运算,以后碰到类似的问题,想必也应该都又思路和方法了。但是,每次解题都要再写一遍这种数据类型,未免也太麻烦了,毕竟作为一大懒人的我,又怎会反反复复干同样的事呢?于是,我想到,可不可以自己编写一个数据类型呢,以后解题时直接调用这样的数据类型不就行了,以下便是我写的加乘的函数。
void Super_int(int n,int* digits)//将int类型的数字存放到数组中方便后面运算 { for (int i = 0; n > 0; n /= 10, i++) digits[i] = n % 10; } void Add(int* digits1, int* digits2) { for (int j = 0; j < 1000; j++) {//两高精度相加并将得到的新数加到高精数digit1中 digits1[j] += digits2[j]; if (digits1[j] > 9) { digits1[j + 1] += digits1[j] / 10; digits1[j] %= 10; } } } void Mul(int* digits,int digit)//高精度与一个int类数字相乘,结果给digits { for (int j = 0; j < 100; j++)digits[j] *= digit;//高精度的一种乘法运算,在乘一个数字时,每个数字都需要乘一遍 for (int j = 0; j < 100; j++) //将相乘之后位数超过9的数字进位,得到新的高精度数字 if (digits[j] > 9) { digits[j + 1] += digits[j] / 10; digits[j] %= 10; } }
以上便是我编的函数代码块,本来想把减法和除法一并做了,但还是时间不够充裕,沉淀的还不够,不过这两个函数足以用来解决1009了。
往后主要还是好好学学算法再练练题,争取写出更高质量的文章。
这次的内容就先到这里了,如果对你有帮助的话,还请点个小小的赞。