数字太大放不下,高精度来补,如何放下超大数字,以及进制转换,高精度真的超好玩。 洛谷1009,1015

简介: 数字太大放不下,高精度来补,如何放下超大数字,以及进制转换,高精度真的超好玩。 洛谷1009,1015

介绍引入

想必大家都遇到过数字大小超过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了。

往后主要还是好好学学算法再练练题,争取写出更高质量的文章。

这次的内容就先到这里了,如果对你有帮助的话,还请点个小小的赞。

相关文章
|
6月前
数字的游戏(数位dp)
数字的游戏(数位dp)
30 0
|
3月前
|
人工智能 算法
第一周算法设计与分析:C : 200和整数对之间的情缘
这篇文章介绍了解决算法问题"200和整数对之间的情缘"的方法,通过统计数组中每个数模200的余数,并计算每个同余类中数的组合数来找出所有满足条件的整数对(i, j),使得\( A_i - A_j \)是200的整数倍。
|
5月前
本周练习题目(高精度,大数值)
本周练习题目(高精度,大数值)
|
存储 算法
算法小白的心得笔记:比较小数点后五位,而不会受到浮点数精度问题的影响。
std::cerr << "\n __" << inum << "__ 计算错误 " << ratio << " 应该是 " << beta3[inum - 1] << std::endl; return 1;
43 0
|
算法
算法:数字涂色
算法:数字涂色
算法练习第九天——只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
|
算法 C++
每日算法系列【kentln供题】模糊的数字
每日算法系列【kentln供题】模糊的数字
【数字IC手撕代码】Verilog偶数分频|题目|原理|设计|仿真(二分频,四分频,六分频,八分频,偶数分频及特殊占空比)
【数字IC手撕代码】Verilog偶数分频|题目|原理|设计|仿真(二分频,四分频,六分频,八分频,偶数分频及特殊占空比)
【数字IC手撕代码】Verilog偶数分频|题目|原理|设计|仿真(二分频,四分频,六分频,八分频,偶数分频及特殊占空比)
|
算法
日拱算法:只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
【数字IC手撕代码】Verilog奇数分频|题目|原理|设计|仿真(三分频,五分频,奇数分频及特殊占空比)
【数字IC手撕代码】Verilog奇数分频|题目|原理|设计|仿真(三分频,五分频,奇数分频及特殊占空比)
【数字IC手撕代码】Verilog奇数分频|题目|原理|设计|仿真(三分频,五分频,奇数分频及特殊占空比)