【算法基础】基础算法(二)--(高精度、前缀和、差分)(下)

简介: 【算法基础】基础算法(二)--(高精度、前缀和、差分)(下)

【算法基础】基础算法(二)--(高精度、前缀和、差分)(上)https://developer.aliyun.com/article/1514661?spm=a2c6h.13148508.setting.29.4b904f0ejdbHoA

2、注意事项
a. 长度不一致

首先进行比较判断,如果 A < B,则将 0 添加到结果 C 中,得商为 0,并将余数 r 赋值为 A,得 r == A,然后返回结果 C。


b. k 的作用

用于记录每次除法计算中的商。


c.  去除前导 0

这段代码有两处去除前导 0 的地方,其中一处含义与之前一致,另外一处是:如果余数 r 的长度 > 1 且末尾元素为 0,则将末尾的 0 删除,保持余数的最小表示形式,这段代码的目的是去除余数前导的零。


d. 代码解释
vector<int> div(vector<int> &A, vector<int> &B, vector<int> &r) {
vector<int> C;
    if (!cmp(A, B)) {
        C.push_back(0);
        r.assign(A.begin(), A.end());//使得r和A的内容一致 
        return C;
    }
    int j = B.size();
    r.assign(A.end() - j, A.end());//将A的末尾与除数B长度相同的部分赋值给变量r
    while (j <= A.size()) { //循环执行除法运算 直到处理完所有的被除数
        int k = 0;//记录每次除法计算中的商
        while (cmp(r, B)) {//直到余数r小于除数B为止
            r = sub(r, B);//将r减去除数B得到新的余数r
            k ++;//每执行一次减法运算,商的个数k就+1
        }
        C.push_back(k);//将每次除法计算得到的商k添加到C的末尾 用于存储所有的商
        if (j < A.size())
            r.insert(r.begin(), A[A.size() - j - 1]);//将下一个被除数数字添加到余数r 
        if (r.size() > 1 && r.back() == 0)
            r.pop_back();//去除余数前导的零
        j++;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0)
        C.pop_back();
    return C;
}

(3)练习

794. 高精度除法 - AcWing题库


二、前缀和与差分

1、前缀和

前缀和可以用于快速计算一个序列的区间和,也有很多问题里不是直接用前缀和,但是借用了前缀和的思想。

(1)一维前缀和

预处理出一个前缀和数组后,要求一段区间和可以使用O(1)的时间复杂度快速求出。

【公式】

预处理 s [ i ] = a [ i ] + a [ i - 1 ]

求区间 [ l r ]: sum = s [ r ] - s [ l - 1 ]

" 前缀和数组 " " 原数组 " 可以合二为一

给定一个 a 数组,请求出它的前缀和数组 s :

那么 a 数组的前缀和数组为:

a 数组与 s 数组之间满足:s[i] = a[0] + a[1] + a[2] + … + a[i]

由于我们在计算前缀和时,为了更加方便,我们会将数组下标从 1 开始存入和读取。

所以,我们的 s 前缀和数组为: s[i] = a[1] + a[2] + … + a[i]

如果要求某个区间的和该怎么办?

用以上的例子,我们想求 a 数组中下标从 3 到 6 的数值的和。如下图:

前缀和原理分析可知:a[3] + a[4] + a[5] + a[6] = s[6] - s[2]


【一维前缀和模板】

🔺记忆!

const int N=100010;
int a[N];
int main(){
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)a[i]=a[i-1]+a[i];
    scanf("%d",&m);
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",a[r]-a[l-1]);
    }
    return 0;
}

写法二:

const int N=100010;
int a[N], s[N];
int main(){
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
    scanf("%d",&m);
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r]-s[l-1]);
    }
    return 0;
}

【练习】

795. 前缀和 - AcWing题库


(2)二维前缀和

计算矩阵的前缀和: s [ x ][ y ] = s [ x - 1 ][ y ] + s [ x ][ y - 1 ] - s [ x - 1 ][ y - 1 ] + a [ x ][ y ]

以 ( x1 y1 ) 为左上角, ( x2 y2 ) 为右下角的子矩阵的和为:

计算子矩阵的和: s = s [ x2 ][ y2 ] - s [ x1 - 1 ][ y2 ] - s [ x2 ][ y1 - 1 ] + s [ x1 - 1 ][ y1 - 1 ]


思路二:

假如我们要求 s[2][3] ,可以根据画图来理解。

s[2][3] 实际上等于下图中绿色区域中数值的和。

我们又可以将这绿色区域划分成以下几种。

我们可以用表达式表示成:s[2][3] = s[2][2] + s[1][3] - s[1][2] + a[2][3]

因为我们在求 s[2][2] 和 s[1][3] 时会求和两次 s[1][2], 所以我们需要再减去一次 s[1][2]。


【二维前缀和模板】

🔺记忆!

int s[1010][1010];
int n,m,q;
 
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",&s[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];
    while(q--){
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]);
    }
    return 0;
}

写法二:

int a[1010][1010], s[1010][1010];
int n,m,q;
 
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",&s[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);
        printf("%d\n",s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]);
    }
    return 0;
}

【练习】

796. 子矩阵的和 - AcWing题库


2、差分

差分是前缀和的逆运算,对于一个数组 a ,其差分数组 b 的每一项都是 a[i] 和前一项 a[i−1] 的差。

注意:差分数组和原数组必须分开存放!

  1. 定义:对于已知有 n 个元素的离线数列 a,我们可以建立记录它每项与前一项差值的差分数组 b:显然,b[1] = a[1] - 0 = a[1]; 对于整数 i ∈ [2,n],我们让 b[i] = a[i] - a[i-1]。
  2. 简单性质:(1)计算数列各项的值:观察 a[2] = b[1]+b[2] = a[1] + (a[2] - a[1]) = a[2] 可知,数列第 i 项的值是可以用差分数组的前 i 项的和计算的,即 a[i] = b[i] 的前缀和

(1)一维差分

给区间 [ l r ] 中的每个数加上 c :b [ l ] += c b [ r + 1 ] -= c


【一维差分和模板】

🔺记忆!

using namespace std;
int a[100010],s[100010];
 
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)s[i]=a[i]-a[i-1];// 读入并计算差分数组
    while(m--){
        int l,r,c;
        cin>>l>>r>>c;
        s[l]+=c;
        s[r+1]-=c;// 在原数组中将区间[l, r]加上c
    }
    for(int i=1;i<=n;i++){
        s[i]+=s[i-1];
        cout<<s[i]<<' ';
    }// 给差分数组计算前缀和,就求出了原数组
    return 0;
}

(2)二维差分

给以 ( x1 y1 ) 为左上角, ( x2 y2 ) 为右下角的子矩阵中的所有元素加上 c

b[x1y1] += cb[x2+1y1] -= cb[x1y2+1] -= cb[x2+1y2+1] += c

二维差分用于在一个矩阵里,快速里把矩阵的一个子矩阵加上一个固定的数。也是直接来修改差分矩阵。试想只要在差分矩阵的(x1,y1) 位置加上 c,那么以它为左上角,所有后面的元素就都加上了 c。要让(x2,y2) 的右边和下边的元素不受影响,由容斥原理可以知道,只要在(x2+1,y1) 和(x1,y2+1) 位置减去 c,再从(x2+1,y2+1) 位置加回 c 就可以了。


【二维差分模板】

🔺记忆!

const int N = 1e3 + 10;
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()
{
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> 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;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);//加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]; //二维前缀和
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}
⚪注意事项
为什么构建差分数组时,是插入i,j,i,j?

使用相同的坐标来作为左上角和右下角的坐标是没有问题的,因为这样可以确保只插入一个元素


相关文章
|
2月前
|
存储 算法 Java
前缀和算法
本文介绍了前缀和及其变种在解决区间求和问题中的应用。首先,一维前缀和可通过预处理数组快速求得任意区间的和。接着,二维前缀和扩展了这一思想,适用于矩阵操作。此外,文章探讨了如何利用前缀和解决诸如“寻找数组中心下标”、“除自身以外数组的乘积”等问题,并进一步讲解了涉及哈希表优化的“和为 K 的子数组”等相关题目。最后,通过实例展示了如何在矩阵中高效计算特定区域的元素之和。文中包含代码示例与图解说明,便于理解。
42 0
前缀和算法
|
1月前
|
人工智能 算法 BI
一篇带你速通差分算法(C/C++)
一篇带你速通差分算法(C/C++)
|
1月前
|
人工智能 算法 C++
一篇带你速通前缀和算法(C/C++)
一篇带你速通前缀和算法(C/C++)
|
3月前
|
算法 C++
【算法】前缀和算法——和可被K整除的子数组
【算法】前缀和算法——和可被K整除的子数组
|
3月前
|
算法
【算法】前缀和算法——和为k的子数组之和
【算法】前缀和算法——和为k的子数组之和
|
3月前
|
算法
【算法】前缀和——除自身以外数组的乘积
【算法】前缀和——除自身以外数组的乘积
|
27天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
12天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
13天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
14天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。