前缀和与差分--算法

前缀和

前缀和本质上是等差数列

前缀和是一种常见的算法思想,适用于处理与数组区间求和相关的问题,能够提高查询效率 

那么什么时候会想到前缀和呢?

1)高频的区间与查询:当你需要频繁查询数组或矩阵的某些区间的总和时,前缀和可以极大的优化效率

以下为几个场景:1,频繁区间和查询,和为特定值的子数组,二维区域求和,统计特定性质的子数组

以下为前缀和795 练习题

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 100010;
int s[N],a[N];

int main()
{
    int m,n;
    cin >> n >> m;
    for(int i = 1;i<=n;i++)
    {
        cin >> a[i];
    }
    for(int i = 1;i<=n;i++)
    {
        s[i] = s[i-1] + a[i];
    }
    
    while(m--)
    {
        int l,r;
        cin >> l >> r;
        cout << s[r] - s[l-1] << endl;
    }
    return 0;
}

这就是应用的前缀和,首先输入值,然后对每一个位置的区间和为等差数列的公式,最后在主函数里面分别输入左边界和右边界,然后从第一个位置到最右边减去第一个位置到做左边的左边的数。

子矩阵的和

这里本质上也是运用的前缀和,但是有一定的技巧‘

已混点:就是坐标,明确坐标时在区间内

#include<iostream>
using namespace std;

const int N = 1010;
int s[N][N],a[N][N];

int main()
{
    int m,n,q;
    cin >> n >> m >> q;
    for(int i = 1;i<=n;i++)
    {
        for(int j = 1;j<=m;j++)
        {
            cin >> a[i][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;
        cin >> x1 >> y1 >> x2 >> y2;
        
        cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] << endl;
    }
    return 0;
}

这个一开始不好理解,多理解就好啦

差分

什么是差分呢?首先理解

以下为介绍:

差分算法 是一种高效处理 区间修改 问题的算法技巧,主要适用于一维或者二维数据,主要是勇敢记录变化量来简化区间操作

核心思想:差分数组记录相邻元素的差值,通过差分数组进行操作,可以快速完成区间内的加减更新

那么好处是什么呢?

高效处理多次区间操作: 对区间的直接修改需要遍历整个区间效率较低

差分算法只需要记录变化,查询复杂度会为O(1)

内存消耗小,差分数组大小与原数组相同,无需额外存储每次操作的结果

#include<iostream>
using namespace std;

cosnt int N = 100010;
int a[N];

int main()
{
    int n,m;
    cin >> n >> m;
    for(int i = 1;i<=n;i++)
    {
        cin >> a[i];
        b[i] = a[i] - a[i-1];
    }
    int l,r,c;
    while(m--)
    {
        cin >> l >> r >> c;
        b[l] += c;
        b[r + 1] -= c;
    }
    for(int i = 1;i<=n;i++)
    {
        a[i] = b[i] + a[i-1];
        cout << a[i] << ' ';
    }
    return 0;
}

这里画一个图

你就理解了

大体上就是这个思路

但是如果按照上面的思路的话可能会显示段错误,反正我是这样的

那么应该怎么改进呢?

段错误: 最常见的原因是数组下标越界或者使用了未初始化的内存

比如说如果读到人r = n 这里会导致越界,所以要特殊说明

#include <bits/stdc++.h>
using namespace std;

// 如果 n 上限是 10^5,通常开到 10^5+5 或更大,避免下标 r+1 越界
static const int MAXN = 100000 + 5;

int a[MAXN], b[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    // 读入原始数组
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    // 为了安全,显式地设置 a[0] = 0,
    // 如果 a、b 是全局 static/全局数组,在 C++ 中会默认初始化为 0,但加上更稳。
    a[0] = 0;

    // 构建差分数组:
    // b[i] = a[i] - a[i-1]
    for (int i = 1; i <= n; i++) {
        b[i] = a[i] - a[i - 1];
    }

    // 读入 m 次操作,每次把区间 [l, r] 的元素都加 c
    while (m--) {
        int l, r, c;
        cin >> l >> r >> c;

        // 差分区间加法
        b[l] += c;

        // 如果 r < n,再对 b[r+1] -= c,避免访问越界
        if (r < n) {
            b[r + 1] -= c;
        }
    }

    // 通过差分数组 b 重新计算出 a
    // a[i] = a[i-1] + b[i]
    // 注意这里若要从头开始计算,需要把 a[0] 设成 0
    for (int i = 1; i <= n; i++) {
        a[i] = a[i - 1] + b[i];
    }

    // 输出结果
    for (int i = 1; i <= n; i++) {
        cout << a[i] << (i == n ? '\\n' : ' ');
    }

    return 0;
}

差分矩阵

这个我认为是难度最大的

输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。

输出格式

共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围

1≤n,m≤1000,

1≤q≤100000,

1≤x1≤x2≤n,

1≤y1≤y2≤m,

−1000≤c≤1000,

−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2

这个是题目

下面我画一个图,类似于子矩阵的和

相信这样大家就理解啦

#include<iostream>
#include<cstdio>
using namespace std;
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);
    }
    
    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;
}

ok了,这个是1月12日的差分与前缀和整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值