【高精度加减乘除法、一维二维前缀和&&差分】思路讲解及代码实现

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。

🌈个人主页:主页链接

🌈算法专栏:专栏链接

     现已更新完KMP算法、排序模板,之后我会继续往里填充内容哒。

🌈LeetCode专栏:专栏链接 

    目前在刷初级算法的LeetBook 。若每日一题当中有力所能及的题目,也会当天做完发出

🌈代码仓库:Gitee链接

🌈点击关注=收获更多优质内容🌈

 

用一篇Blog来讲解下最近学到的高精度加减乘除法、一维二维前缀和&&差分等算法,为日后的刷题打下坚实的基础。

目录

高精度:

高精度加法:

加法模板:

至此高精度加法结束.

高精度减法:

减法模板:

至此高精度减法结束.

高精度乘法:

 乘法模板:

至此高精度乘法结束.

高精度除法:  

除法模板:

至此高精度除法完结.

高精度总结:

前缀和:

一维前缀:

一维前缀和模板:

至此一维前缀和完结.

二维前缀和:

二维前缀和模板:

至此二维前缀和完结.

差分:

一维差分:

一维差分模板:

至此一维差分完结.

二维差分:

二维差分代码模板:

至此二维差分完结.

完结撒花:


高精度:

高精度加减乘除法旨在解决两个很大的数,进行数学运算时会产生的溢出的情况。他们的形式通常长这样。

高精度加法:A+B    高精度减法:A-B

高精度乘法:A*a     高精度除法:A*a

其中A、B的范围为:len(A)<=10^6       注意:这是数位长度,也就是位数

        a的范围为:a<=1000

所以当实际情况中出现如此庞大的数进行运算时仅仅依靠数据类型定义进行简答的运算是不够的

我们需要模拟最开始学习算术时的过程。

在计算机中我们如果想在容器的末尾插入一个数据是很容易的,但若我们想要在容器的开头插入一个数据是十分消耗时间的,为了防止在头插入的这种情况,我们决定将A B反过来进行存储,也就是“小端存储”即低位存储到数组的开头。这样可以解决进位运算麻烦的问题。

高精度加法:

试想一下:我们最开始学习加法的时候是不是按照下面的竖式来学习的?

先将最小位相加,若满10则进1到高位,将相加后的结果作为结果的对应位。

其中,绿色表示进位,蓝色表示运算结果

 那么将这个算式抽象为我们上面所讲的A+B的形式就是

总的来说,高精度加法就是将A、B拆成一个个Ai,Bi,之后让Ai+Bi,判断是否需要进位,将运算完的结果填入Ci。

在函数中设置一个临时变量t,依次遍历A与B中的各个位数,然后加在t中,将t%10的结果放入C中,也是从小到大的“小端存储”,最后再将t/10重置状态,若大于10则会再下一次运算中进1.

加法模板:

#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+10;
vector<int>Add(vector<int>&A,vector<int>&B)
{
    vector<int>c;
    int t=0;
    for(int i=0;i<A.size()||i<B.size();i++)
    {
        if(i<A.size())t+=A[i];
        if(i<B.size())t+=B[i];
        c.push_back(t%10);
        t/=10;
    }
    if(t)c.push_back(1);
    return c;
}
int main()
{
    string a,b;
    vector<int>A,B;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');//a 123456 A[654321] 
    for(int i=b.size()-1;i>=0;i--)B.push_back(b[i]-'0');//b 45678  B[87654]
    vector<int>c=Add(A,B);
    for(int i=c.size()-1;i>=0;i--)cout<<c[i];
    getchar();
    return 0;


}

至此高精度加法结束.

高精度减法:

高精度加法与高精度减法大同小异,也是按照上方所述"小端存储"的思想,唯一与加法不同的点就是加法是进位,而减法是借位.

将t作为答案数组中第i位数位时,直接传入((t+10)%10)即可

若t大于0加上的10则会被mod,若t小于0则传入的是加上10的结果

 当Ai-Bi小于0时,要加上10,并将结果作为Ci答案数组中第i位的数.并将tmp置为-1,以参与下次运算.

反之,将t置为0

最后去除前导0,认真观察上述算式产生的结果,会发现当A[i]为1时再被借走1此时就会往答案数组 当中存一个0.例如:012这样的结果,这在我们正常的数字表达中是错误的,所以我们需要将0去除.

减法模板:

#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+10;
//高精度减法 A-B
bool cmp(vector<int>&A,vector<int>&B)
{
    if(A.size()!=B.size())return A.size()>=B.size();
    for(int i=A.size()-1;i>=0;i--)
        if(A[i]!=B[i])return A[i]>B[i];
    return true;
}
vector<int>sub(vector<int>&A,vector<int>&B)
{
    vector<int>c;
    int t=0;
    for(int i=0;i<A.size()||i<B.size();i++)
    {
        t=A[i]-t;
        if(i<B.size())t-=B[i];
        c.push_back((t+10)%10);
        if(t>=0)t=0;
        else t=1;
    }

    while(c.size()>1)
    {
        if(c.back()==0)
            c.pop_back();
        else break;
    }
    return c; 
}
int main()
{
    string a,b;
    vector<int>A,B;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');//a 123456 A[654321] 
    for(int i=b.size()-1;i>=0;i--)B.push_back(b[i]-'0');//b 45678  B[87654]
    vector<int>c;
    if(cmp(A,B))
        {
            vector<int>c=sub(A,B);
           for(int i=c.size()-1;i>=0;i--)cout<<c[i];
        }
    else
        {
            vector<int>c=sub(B,A);
            cout<<"-";
           for(int i=c.size()-1;i>=0;i--)cout<<c[i];
        }
    return 0;


}

至此高精度减法结束.

高精度乘法:

乘法与加法类似,也涉及到了一个"进位"的问题,需要注意由于乘法是A*b的模型,因此处理数据时仅需要将A"小端存储"即可,用A[i]按位*b将最后的结果处理放入数组即可.

如上所示,将6*82的结果放入临时变量t中.然后将t%10放入答案数组中,再将t/10,去除尾数.再将剩下的数放入新的一轮循环当中t=t+A[i]*b. 当t/10==0时表示临时存储中没有数字了,也即运算结束.

 也就是下面这个模型,同样需要去除前导0

 乘法模板:

#include<iostream>
#include <vector>
#include<vector>
using namespace std;
vector<int>Mul(vector<int>&A,int b)
{
    vector<int>c;
    int t=0;
    for(int i=0;i<A.size()||t;i++)
    {
        t+=A[i]*b;
        c.push_back(t%10);
        t/=10;
    }
}
int main()
{
    string a;
    int b;
    vector<int>A;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
    auto c=Mul(A,b);
    for(int i=c.size()-1;i>=0;i--)cout<<c[i];
    return 0;
}

至此高精度乘法结束.

高精度除法:  

与上面三个不想同的是,除法不需要将数据"小端存储",但为了我们能写一个接口就实现四个算术,这里我们依然将除法中的A进行"小端存储"

先将除数b与A[i]进行除法判断,输出0,放入c当中.则令t=A[i],进入下一层循环,此时t初始化为t=t*10+A[i]继续与除数b进行判断,将输出的结果放入c当中.

因为我们采取"小端存储"的方式,所以输出到答案数组当中的数字是反的,例如488/4=122,而我们放到答案数组当中的数字就是122,但是!我们为了统一与其他三个接口的情况,我们最后输出是按221的形式输出的,所以再此之前,我们需要将答案数组先反过来

除法模板:

#include <algorithm>
#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+10;
//A/B 余C
vector<int>Div(vector<int>&A,int b,int &t)
{
    vector<int>c;
    for(int i=A.size()-1;i>=0;i--)
    {
        t=t*10+A[i];
        c.push_back(t/b);
        t%=b;
    }
    reverse(c.begin(), c.end());
    while(c.size()>1&&c.back()==0)
        c.pop_back();
    return c;
}
int main()
{
    string a;
    int b,t=0;
    vector<int>A;
    cin>>a>>b;
    for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');//a 123456 A[654321] 
    vector<int>c=Div(A,b,t);
    for(int i=c.size()-1;i>=0;i--)cout<<c[i];
    cout<<endl<<t<<endl;
    getchar();
    return 0;


}

至此高精度除法完结.

高精度总结:

高精度计算总的来看就是将一大堆数拆成一个个数进行处理,用到的知识也仅是用计算机模拟手动运算的过程.

前缀和:

用来计算某一个区间的和

一维前缀:

前缀和的计算有以下关系

很容易可以看出 s[i]=s[i-1]+a[i],这就是求一维前缀和的法则

若需要求[l,r]区间内的和,则s[l,r]=s[r]-s[l-1] 例如:求[3,5]即用s[5](a1-a5)-s[2](a1-a2)

注意!!!a的下标是从1开始的,而不是0,若改为0,按照上面的法则简单推导则会出现s[-1]的情况 这显然不可取.

一维前缀和模板:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const int N=100010;

int n,m;
int a[N],s[N];
int main()
{
    int n=0,m=0;s[0]=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);//前缀和的处理 i从1开始
    for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
    while(m--)
    {
        int l=0,r=0;
        scanf("%d%d",&l,&r);//区间的计算
        cout<<s[r]-s[l-1]<<endl;
    }
}

至此一维前缀和完结.

二维前缀和:

先从图形当中形象的感受下二位前缀和的是什么. 每一个点都表示aij

蓝色框框框起来的就是S[3][3],表示这个框里所有数的和

 接下来我们来看看如何求s[3][3]

s[3][3]=绿色的框+紫色的框+上外面的点a[3][3]-阴影部分(绿色与紫色相加的时候这个面积被加了两遍所以需要减去其中一块)

那么绿色的框=s[2][3] 紫色的框=s[3][2] 而阴影部分则为s[2][2],

所以s[3][3]=s[2][3]+s[3][2]-s[2][2]+a[3][3]

推导出 s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]这就是我们二维前缀和公式

若需要求出[i1][j1] - [i2][j2]区间和,s[2][2]-[3][3]=s[3][3]-黄色的框-紫色的框+红色部分(黄色与紫色相减的时候这个面积被减了两遍所以需要加上其中一块)

 s[2][2]-[3][3]=s[3][3]-s[1][3]-s[3][1]+s[1][1]

可以推出s[i1][j1]-[i2][j2]=s[i2][j2]-s[i1-1][j2]-s[i2][j1-1]+s[i1-1][j1-1]

 二维前缀和公式s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]

 二位前缀和区间公式为s[i1][j1]-[i2][j2]=s[i2][j2]-s[i1-1][j2]-s[i2][j1-1]+s[i1-1][j1-1]

二维前缀和模板:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const int N=1010;

int n,m;
int a[N][N],s[N][N];
int main()
{
    int n=0,m=0,q=0;s[0][0]=0;
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)//前缀和的处理 i从1开始
        for(int j=1;j<=m;j++) 
            scanf("%d",&a[i][j]);    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
    while(q--)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);//区间的计算
        cout<<s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]<<endl;
    }
    return 0;
}

至此二维前缀和完结.

差分:

让特定区间的前缀和都可以加上一个值(r)

一维差分:

先来介绍下什么是差分.bi=ai-ai-1 也就是 b1=a1(因为没有a0) b2=a2-a1以此类推

 因为b1=a1 b2=a2-a1 b3=a3-a2 所以可以看出 a2=b2+b1   a3=b3+b2 

所以有这样一条结论 a是b的前缀和,b是a的差分

假设我们想让[1,3]的区间的前缀和都加上r

那么就有a1=b1+r

                a2=b1+b2+r

                a3=b1+b2+b3+r

那么a4呢?a4这时候可不满足区间条件 但又因为是前缀和,所以这里让a4=b1+b2+b3+r+b4-r

所以,要想在[l,r]区间内插入r  就有    b[l]+=r       b[r+1]-=r     这一公式

那怎么来求差分呢?差分可以理解为在[i,i]这个区间内插入了ai,也就是insert(i,i,a[i])

到这一维差分的问题我们就解决了

一维差分模板:

#include<iostream>
using namespace std;
const int N=100000;
int n,m;
int a[N],b[N];

void insert(int l,int r,int c)
{
    b[l]+=c;
    b[r+1]-=c;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)insert(i,i,a[i]);
    while(m--)
    {
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        insert(l,r,c);
    }
    for(int i=2;i<=n;i++)b[i]+=b[i-1];
    for(int i=1;i<=n;i++)cout<<b[i];
    return 0;

}

至此一维差分完结.

二维差分:

类似于二维前缀和的问题,通过一维差分我们已经知道

差分是对前缀和区间首项加上R,在区间下一项减去R来改变前缀和的值

二维差分与二维前缀和十分类似 ,我们还是画图来理解

 

 我们要求的区间是b[2][2]--b[4][4]黑色区域

根据一维差分公式 我们将b[2][2]+r那么影响的区间是整块紫色的也就是从(2,2)(6,6) 

但我们需要的只是(2,2)(4,4) 所以我们需要减去相应的块

从图中来看也就是 黑色区域=紫色区域-绿色区域-土色区域+橙色区域(因为被减了两次)

那么也就是b[2][2]-[4][4]=  b[2][2]+=r    b[4][5]-=r    b[5][2]-=r   b[5][5]+=r

那么抽象出来看:给定一个范围[x1][y1] [x2][y2] +r  对区间的处理就为

b[x1][y1]+=r   b[x2+1][y1]-=r   b[x1][y2+1]-=r  b[x2+1][y2+1]+=r 

二维差分代码模板:

#include <cstdio>
#include<iostream>
#include <stdio.h>
using namespace std;
int n=0,m=0,q=0;
const int N=1010;
int a[N][N],b[N][N];
void insert(int x1,int y1 ,int x2,int y2,int c)//二维差分公式
{
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            insert(i,j,i,j,a[i][j]);
        }
    }
    while(q--)
    {
        int x1,y1,x2,y2,c;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
        insert(x1,y1,x2,y2,c);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+b[i][j];//求前缀和公式
        }

    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            cout<<b[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

至此二维差分完结.

完结撒花:

🌈本篇博客的内容【高精度加减乘除法、一维二维前缀和&&差分 思路讲解及代码实现】已经结束。

🌈若对你有些许帮助,可以点赞、关注、评论支持下博主,你的支持将是我前进路上最大的动力。

🌈若以上内容有任何问题,欢迎在评论区指出。若对以上内容有任何不解,都可私信评论询问。

🌈诸君,山顶见!

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ppeua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值