1 条题解

  • 0
    @ 2025-8-24 21:34:27

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar stone_juice石汁
    敲稀有滴物种石汁qaq

    搬运于2025-08-24 21:34:27,当前版本为作者最后更新于2018-11-18 17:51:24,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题解 /【学习笔记】-- 高精度减法

    修改日志:

    • 1、19/3/31 修改:优化了批注,写的更易懂一些。

    • 2、19/8/8 修改:增加了图片,细节更多,调整码风,解决了一些小BUG,科普了一些无关紧要的知识。(重大修改)


    • 1、intint的悲伤与高精度的需求。

      我们往常在作运算的时候,往往直接用运算符完成运算(譬如a - b)。这当然非常地简便。

      但是当数据范围特别大的时候,你去用运算符做减法往往得不到正确的结果,为什么??

      首先,在c++c++里,每一种实数变量型都是有一个数据储存范围的。譬如intint型,它的数据范围就为 2147483648<int<2147483647-2147483648 < int < 2147483647,也就是 231<int<2311-2^{31} < int < 2^{31} -1。一旦你给一个int型变量存一个大于这个范围的数,它就会产生溢出,然后你存的数字会变成另外一个非常神奇的数字 。可以自己在线下试一试哦。

      不要和我提Py和java!

      这个时候,你存的数字本身就已经发生错误,就不用指望他来运算了。

      当然也有解决办法,譬如开longlonglong long。但是当数据范围高到恐怖的地步的时候(甚至大于10100010^{1000}),别说longlonglong long了,开挂神器__int128int128都救不了你。

      这个时候,就需要高精度算法来解决问题。


    • 2、数组搞定高精度数字

      上面我们有提到过,在数据范围很大的时候,任何变量型已经无法满足我们的需求。这个时候怎么办?

      接下来介绍一种最传统的做法:开数组。

      我们开一个数组。这个数组开到多大?就要看你要存的数的位数有多少。若想存一个20002000位的数,就把数组开到20002000以上。

      你位数可以多啊,你多一位 位数,我就把数组多开一个空间。看谁斗得过谁

      而本题甚至开到了1008610086位,所以我选择把数组开到1050010500位。(开多点没坏处)

      想必这样说大概都清楚了。我们用数组的每一个空间来存每一位数的那个数字。

      这句话是什么意思呢?我们打个比方。

      比如说我们现在要存123123这个数,把它存进nana数组里。

      我们可以取出最后一位(个位)33,放到na[1]na[1]中。于是na[1]=3na[1] = 3

      同理,我们取出2,12,1放进数组,于是就有:

       na[1] = 3;
       na[2] = 2;
       na[3] = 1;
      
       //也就是: na[MAXN] = {3, 2, 1};
       
      

      此时,na[i]na[i]就代表了此数的最后ii

      但是

      我们并不选择直接一个一个输入每位数字,把数字存进数组。

      我们选择使用字符串 ,像这样:

      string a, b;
      cin >> a >> b;
      for(int i = a.size(); i > 0; i --)na[i] = a[a.size() - i] - '0';
      for(int i = b.size(); i > 0; i --)nb[i] = b[b.size() - i] - '0';
      //这个代码底下有解释
      

      为什么这样做?

      首先,字符串输入两个数字,我们可以直接地知道每个数字的长度,也就是位数(字符串aa的长度为a.size()a.size(),依次类推)。知道长度就可以直接给forfor循环服务。

      其次,字符串它输入方便啊,一个cincin就完成了,也不必去检验什么输入到空格之类的了。

      当然,最方便的是可以将字符串扔进子函数里(这个在最后有拓展)

      当然:将字符串转化进数组需要一定技巧:

      1、首先,字符串里同样可以用下表形式表示某位的数(譬如a[i]a[i]),但是和我们之前的存法相反,a[0]a[0] 表示的数字的最高位,次高位是a[1]a[1]。他是从00且是从高位算起的,和我们之前说的从低位开始存恰好相反。

      2、其次,字符串里每个数其实都是charchar型的。虽然看起来和intint型没什么不同,但是想把charchar型转化成intint型需要通过一个介质:ASCIIASCII码。

      每个charchar字符都有对应的ASCIIASCII码,而090-9ASCIIASCII码是485748 - 57如果你尝试把一个charchar型的00存进intint,那么存进去的则是00ASCIIASCII码,也就是4848

      这两个情况怎么解决?

      • 1、第一个问题,我们只需要把数组的第ii位和字符串的第(位数 i- i)位对应起来就可以了。这是很好证明的。

      假设我们要存一个33位数,它的每一位:

      用数组表示:na[1],na[2],na[3]na[1], na[2], na[3]

      用字符串表示:a[31],a[32],a[33]a[3 - 1], a[3 - 2], a[3 - 3](这里千万要注意字符串下标是从00开始存的,也就是a[0]a[0]表示最高位)

      代码中手打出来就可以了。

      • 2、第二个问题,090-9ASCIIASCII码不是485748 - 57么,那么把字符引入intint型的时候,我们只需要引入时把每个数减去4848就行了。 485748-57减去48=0948 = 0-9 道理都懂,对吧。

      当然你可以把4848写成0'0'0'0'就代表着00ASCIIASCII

      于是这就有了我上面给出的代码:

       string a, b;
       cin >> a >> b;
      for(int i = a.size(); i > 0; i --)na[i] = a[a.size() - i] - '0';//其实这里正这倒着循环真的无所谓
      for(int i = b.size(); i > 0; i --)nb[i] = b[b.size() - i] - '0';
       
      
      • 于是乎,我们就有了两个数组,存了两个高精度数字。
    • 3、小学计算教你做减法

      数字存进数组去了,那么问题来了,如何让两个数组作减法??

      答案很简单:小学竖式运算!

      什么?你居然教我减法用竖式???

      好吧,可能各位做减法熟能生巧,不需要列竖式就可以口糊出来。但是你要知道,计算机并不会做数组相减的运算。于是我们就需要“教他”算。

      小学老师怎么教你算减法?当然是竖式运算。

      具体做法就是:把两个数字的每一位对齐,从低位到高位,同一位逐次相减,如果减不够就向后一位借位。(小学竖式借位甚至还要向借的位打标记防止你忘了)

      计算机的竖式运算同样是这个道理。我们假设有两个存了数字的数组na,nbna,nb

      我们把两个数的每位对齐,刚刚我们提到:na[i]na[i]代表aa这个数的第ii位。同理:nb[i]nb[i]代表bb这个数的第ii位。

      那么,我们只需要做的就是na[i]nb[i]na[i] - nb[i]就行了。这就代表了aa的第ii位减去了bb的第ii

      我们用表格演示一下456789456789减去135353135353,其中,ansans数组用来存储答案

      可以看到,每一位依次相减,最后传进答案数组,这个相减的过程就完成了。

      用代码表示,可以表示成:

      ans[i] = na[i] - nb[i];
      

      可是!以上问题没有考虑过借位的情况!

      如果要借位,我们怎么处理??

      根据小学竖式的博大精深来看,我们可以向高位借位,然后再相减。

      这个借位如果要说明白点,就是高位减去一个11,给低位一个1010加上然后再相减。

      具体的,如果运算341934 - 19494 - 9明显减不够,于是:

       34 - 19 = (30 + 4) - (10 + 9) = (20 + 14) - (10 + 9) 
       = (20 - 10) + (14 - 9) = 10 + 5 = 15
      

      上面的例子中,3030很明显借了101044,然后1414 - 99进行运算

      这个东西放到我们数组相减也适用。简单来说,当一位减不够另一位时,就从高位借1010过来再减,当然,高位会比原来少11

      我们再用表格举个例子:456789147791456789 - 147791

      QQ浏览器截图20190807215325.png

      这样就成功处理了减位的问题。

      用代码就这样表示:

       int maxl = max(a.size(), b.size());
       /*
       找到两个数中的最大位,为for循环服务 
       如果两个数位数不相等,相减也无妨,因为位数少的数那部分被0补齐,减下去不影响
       */
       for(int i = 1; i <= maxl; i ++)
       {
       	if(na[i] < nb[i])//减不够
       	{
       		na[i + 1] --;//借位
       		na[i] += 10;//到低位去
       	}
       	ans[i] = na[i] - nb[i];//相减
       }
      

      于是,相减代码就是这样了

    • 4、看似已完成,输出却全是坑

      你以为这样就做完了?评测姬告诉你:Too Young Too Simple

      输出里还有一些坑,把你坑的头皮发麻。

      平常的输出方式是什么?我们现在知道了两个数的最大位,上面也有提到过。我们用maxlmaxl表示。

      因为我们是从个位,逐渐递增存每位上的信息,但是我们要从最高位开始输出,也就是我们要先输出最高位。 这倒是和字符串有点相似。

      于是我们采用倒着输出方法,代码大概是这个样子。

      for(int i = maxl; i > 0; i --)cout << ans[i];
      

      但是!坑来了。

      • 坑点1、

      如果你用100011000010001 - 10000,会发生什么?

      当然你会知道它应该输出11。可你的计算机不这么认为。

      依照上面的输出方法,此时maxlmaxl55那么,它输出的就不是11,而是0000100001,因为他会输出maxlmaxl次,也就是55次,并且是从ans[5]ans[5] 输到 ans[1]ans[1]但此时:ans[2]ans[5]ans[2]-ans[5]都已经为00

      所以我们不得不考虑,在相减后位数下降的情况下,不特殊处理就会多输出若干个00

      解决方法很简单,输出前加上这么一句话即可:

      while(ans[maxl] == 0)maxl --;
      //如果最高位为0,就一直减小最高位,直到不为0为止。
      

      由于maxlmaxl直接为输出的forfor循环服务,减小maxlmaxl相当于减少了循环输出次数。最后,ans[maxl]ans[maxl]会停留在第一个不为00的位置。

      • 坑点2、

      上面的坑点提到要去除多余的前导零,但是,是不是所有的前导零都是多余的?

      要是答案输出本身就为00,前导零就是他本身。这个时候我们仍然要输出一个00 ,如果我们不处理这个地方,上面处理前导零的代码就会把需要输出的00也给处理掉。

      也就是说,当两减数相等,答案为00,不处理这个地方,代码什么都不会输出。

      所以我们要在最后输出时这么写:

       if(maxl < 1)cout << "0";
      //最大位小于1,也就是最高位为0时输出。
      //最高位为0,显而易见答案就为0了
      //这种情况下前面什么都不会输出,这里额外输出一个0倒也无伤大雅
      
      • 坑点3、

      这里还有一个坑,那就是:相减可能小于00

      显然,我们用上面的相减代码是处理不掉这种情况下的。 那怎么办?

      这个时候就需要手动加特判了。

      稍微想一下,只有在数字 b>ab > a的情况下,aba - b才可能为负。于是我们可以用非常玄学的运算式:

      (ab)=(ba)(a - b)= - (b - a)

      如何实现这个操作?我们在判断 b>ab > a 成立后,可以调用函数swapswap交换aba,b,然后再用一个判断boolbool型变量打上标记。

      交换过后,很明显,运算变为 bab - a ,而 b>ab > a,显然运算不会出错。最后输出的时候,只需要在前面加一个负号即可。

      bool pd;
      if((a < b && a.size() == b.size()) || a.size() < b.size())
      {
      	 swap(a, b);//swap函数作用:交换两数
      	 pd = true;//打上标记
      }
      //----中间省略一堆计算代码-----
      
      if(pd == true)cout << "-";
      //b > a时,a - b < 0,打头输出负号
      
      //----后面省略一堆输出代码-----
      
      

      这里需要一提的是判断 b>ab > a的方法。很显然,这里aba,b都是字符串stringstring型。为什么要这么写?

      这里涉及字典序的比较大小方式。stringstring类型不是不能比大小,而是规则上有所不同

      粗略地概括一下:

      从最高位比起,ASCIIASCII码更大的字符串更大。如果相等,比次高位,以此向下类推。

      所以在stringstring中,串 9>899 > 89 。因为最高位9>89 >8

      当然,像前面几个数如果都相等,位数更大的显然更大。

      例如1234500>123451234500 > 12345

      所以说:在位数相等的时候,我们可以直接利用字符串比大小的性质,来比较两数大小,但又要防止出现 9>899 > 89 这种情况,所以还要保证位数大的数值才更大

      综上所述,得出这么一句判断。

      到此为止:我们得出来了高精度的ACAC代码

    • 上代码!

    #include<bits/stdc++.h>
    #define mian main
    #define QWQ puts("QWQ");
    #define MAXN 10500 
    //define是宏定义,define a b的作用是把a代替为b执行。 
    //这里的意思是把MAXN替换成10500执行 
    //请无视上方两个的宏定义(QWQ) 
    
    using namespace std;
    
    string a, b;
    //选择字符串。因为字符串储存了每个串的长度,可以直接调用。
    int na[MAXN], nb[MAXN], ans[MAXN]; 
    bool pd;
    
    int main()
    {
        cin >> a >> b;
    	if((a < b && a.size() == b.size()) || a.size() < b.size())
    	{
    		swap(a, b);
    		pd = true;
    	}
        for(int i = a.size(); i > 0; i --)na[i] = a[a.size() - i] - '0';
        for(int i = b.size(); i > 0; i --)nb[i] = b[b.size() - i] - '0';
        //将字符串中的信息转化到数组中,数组模拟数字。 
        int maxl = max(a.size(), b.size());
        //找到两个数中的最大位,为for循环服务 
    	for(int i = 1; i <= maxl; i ++)
    	{
    		if(na[i] < nb[i])
    		{
    			na[i + 1] --;
    			na[i] += 10;
    		}
    		ans[i] = na[i] - nb[i];
    	}
    	
    	while(ans[maxl] == 0)maxl --;//防止减后降位,多输出若干0 
    	
    	if(pd == true)cout << "-";//b>a时,a - b < 0 所以打上负号 
    	
    	for(int i = maxl; i > 0; i --)cout << ans[i];
    	if(maxl < 1)cout << "0";
        return 0;
    }
    
    • I Need More!!!

      这个代码的确可以ACAC整道题,但是并不是完美的高精度减法。

      • 当我们需要多次调用高精度运算时,这种写法就显得鸡肋了。

      • 并且,它并不能处理 aabb 为负数的情况。

      • 最重要的是,换种写法逼格更高

      但是这里就不写了,我挂在底下,有需求的可以自行了解哦(主要怕占版面太多 QWQ )

      “就这么点东西根本无法满足我”

    • 特别鸣谢:

      • 洛谷提供的平台

      • 管理员的辛勤审核

      • 大家的支持(没有大家的支持,我可能没有动力去更新Updata)

      希望大家能在评论区留言讨论,我会尽量看的!

    • 1

    信息

    ID
    1132
    时间
    1000ms
    内存
    125MiB
    难度
    2
    标签
    递交数
    0
    已通过
    0
    上传者