前缀和和差分算法

这篇博客详细介绍了前缀和与差分算法。前缀和可以通过递归计算,用于快速求解数组区间和,文章给出了从一维到二维的实例解析。差分操作则是前缀和的逆过程,通过构造新数组b,使得b的前缀和等于原数组a。文中同样以一维和二维为例,阐述了差分的应用。
摘要由CSDN通过智能技术生成

前缀和

  • 假设原始数组为 a 1 , a 2 , a 3 , … , a n a_1, a_2, a_3, \ldots, a_n a1,a2,a3,,an,则前缀和数组为 S i = a 1 , a 1 + a 2 , a 1 + a 2 + a 3 , … , a 1 + a 2 + … + a i S_i = a_1, a_1 + a_2, a_1 + a_2 + a_3, \ldots, a_1 + a_2 + \ldots + a_i Si=a1,a1+a2,a1+a2+a3,,a1+a2++ai
  • 我们可以使用递归来计算 S n S_n Sn
  • 此外,我们可以快速计算一段数组的和

步骤

  • 预处理前缀和数组
  • 用公式求区间和

一维前缀和例题

输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。

输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。

输出格式
共 m 行,每行输出一个询问的结果。

数据范围
1 ≤ l ≤ r ≤ n,
1 ≤ n,m ≤1 00000,
−1000 ≤ 数列中元素的值 ≤ 1000

输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例:
3
6
10

防止数组下标越界,让数组都从1开始

#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int a[N], s[N];
int n, m;

int main(void)
{

    scanf("%d %d", &n, &m);
    
    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];
    
    while(m--)
    {
        int l, r;
        scanf("%d %d", &l , &r);
        printf("%d\n", s[r] - s[l - 1]);
    }
    
    return 0;
}

ios :: sync_with_stdio(false);

让标准的输入和输出不同步

这个作用是提高cin 的读取速度,但是不能再使用sacnf

但是一般还是没有scanf快

一般而言数据输入的规模大于等于一百万建议使用scanf,否则使用cin

二维前缀和例题

输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。

输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

输出格式
共 q 行,每行输出一个询问的结果。

数据范围
1 ≤ n,m ≤ 1000,
1 ≤ q ≤ 200000,
1 ≤ x1 ≤ x2 ≤ n,
1 ≤ y1 ≤ y2 ≤m,
−1000 ≤ 矩阵内元素的值 ≤ 1000

输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:
17
27
21
#include<iostream>

using namespace std;

const int N = 1e3 + 10;

int n, m, q;
int a[N][N], s[N][N];

int main(void)
{
    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++)
            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;
}

差分

  • 给定数组a,我们希望构造一个数组b,使得a是b的前缀和

  • 差分操作是一个逆操作,给定一个数组a,构造一个数组b,其中b[ i ] = a[ i ] - a[ i - 1 ],并且约定a[ 0 ] = 0

  • 这样,数组b的前缀和就是数组a本身。因此,逆操作就是给定数组a,如何构造数组b

  • 假设给定的数组a为[ a1, a2, a3, …, an ]。为了构造b,我们可以按照以下方式操作:

    • 确定b的长度为n

    • 对于b的第一个元素b[ 0 ],由于约定a[ 0 ] = 0,所以令b[ 0 ] = a[ 0 ]

    • 对于b的其他元素b[ i ],根据前缀和的性质,有b[ i ] = a[ i ] - a[ i - 1 ]

给a数组某一连续的区间统一加上一个值

一维差分例题

输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。

输出格式
共一行,包含 n 个整数,表示最终序列。

数据范围
1 ≤ n,m ≤ 100000,
1 ≤ l ≤ r ≤ n,
−1000 ≤ c ≤ 1000,
−1000 ≤ 整数序列中元素的值 ≤ 1000

输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

输出样例:
3 4 5 3 4 2
#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int n, m;
int a[N], b[N];

int insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}


int main(void)
{
    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 = 1; i <= n; i++)
        b[i] += b[i - 1];
    
    for(int i = 1; i <= n; i++)
        printf("%d ", b[i]);
    
    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>
using namespace std;

const int N = 1e3 + 10;
int a[N][N], b[N][N];
int n, m, q;

int 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(void)
{
    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];
    
    for(int i = 1; i <= n; i++)
    {
         for(int j = 1; j ,= m; j++)
             printf("%d", b[i][j]);
        puts("");
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

海大皓月

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

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

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

打赏作者

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

抵扣说明:

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

余额充值