[基础算法]前缀和与差分

目录

  • 一、前缀和
    • 1.1 一维前缀和
    • 1.2 二维前缀和
  • 二、差分
    • 2.1 一维差分
    • 2.2 二维差分


一、前缀和

1.1 一维前缀和

如何求出某一段区间内的和?——这里可以使用前缀和

如果有一个长度为n的数组:a1,a2…an
前缀和:Si = a1+a2+…+ai

1、如何求Si

S0 = 0;
for(i = 1; i <= n; ++i)
{
	S[i] = S[i - 1] + ai;
}

这里for循环是从1开始的,原因是为了保证计算S10 - S0 = 1 - 10区间内的和

2、前缀和的作用

快速求出原数组一段数的和[l,r]
Sr - Sl-1
Sr = a1 + a2 + … + al-1 + al + … + ar
Sl-1 = a1 + a2 + … + al-1
比如求[1,10]的和也就是求S10 - S0
就把S0定为0,此时就是S10

前缀和
[基础算法]前缀和与差分_第1张图片

#include 
#include 

using namespace std;
const int N = 100010;
int arr[N], sum[N];
int main()
{
    int m,n,x;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
    {
        cin >> x;
        sum[i] = sum[i - 1] + x; // 前缀和数组
    }
    while (m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        cout << sum[r] - sum[l - 1] << endl;
    }
    return 0;
}

拓展:

ios::sync_with_stdio(false); // 提高cin的读取速度——不能在使用scanf了

数据规模>一百万建议使用scanf

1.2 二维前缀和

二维前缀和S[i,j]的含义

[基础算法]前缀和与差分_第2张图片
S[i,j]如何计算

S[i,j] = S[i-1,j] + S[i,j-1] - S[i-1,j-1] + a[i,j]

以(x1,y1)为左上角,(x2,y2)为右下角的子矩形所有数的和该如何计算

S(x2,y2,x1,y1) = S(x2,y2) - S(x1-1,y2) - S(x2,y1-1) + S(x1-1,y1-1)
注意:aij表示以i,j坐标为顶点,单位为1的小矩阵,也就是坐标对应的是一个小方格,而非点
因此在二维矩阵中,算一个小矩阵的面积模板为:
Sij = ?

for (i~n)
{
	for(j~m)
	{
		Sij = Si-1,j + Si,j-1 - Si-1,j-1 + aij;
	}
}

[基础算法]前缀和与差分_第3张图片

注意:
前缀和一般for循环是从1开始,因为会涉及i-1,j-1
子矩阵的和
[基础算法]前缀和与差分_第4张图片

#include 
using namespace std;
const int N = 1010;
int sum[N][N], arr[N][N];
int main()
{
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            scanf("%d", &arr[i][j]);
        }
    }
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + arr[i][j]; // 求出前缀和
        }
    }
    while (q--)
    {
        int x1, x2, y1, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        cout << sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1- 1] + sum[x1 - 1][y1 - 1] << endl; // 求子矩阵的和
    }
    return 0;
}

二、差分

2.1 一维差分

差分实际上是前缀和的逆运算
有一个数组:a1,a2,…,an
构造一个B数组:b1,b2,…,Bn
使得ai = b1 + b2 + … bi——a数组是b数组的前缀和
一维构造:
b1 = a1
b2 = a2 - a1
b3 = a3 - a2

bn = an - an-1
证明:
a1 = b1
a2 = b2 + b1
a3 = b3 + b2 + b1
因此只要有b数组,通过前缀和运算,就可以在O(n)时间内得到a数组
b数组称为a的差分,a称为b的前缀和
差分的作用:
在一个区间[l,r]内,要使得a数组的每一个值都加上一个c,使得a(l) + c ,a(l+1) + c ,…,a® + c时间复杂度就是O(n),用差分就可以达到O(1)

for(l~r)
{
	a[i] + c;
}

对于b数组某一个位置修改,就会影响到a数组从这个位置开始后面每一个位置的修改——因为b数组的前缀和就是a数组
让b数组中b(l)+c,a数组就会受到影响:
a(l) + c, a(l + 1) + c … a(n) + c ——因为a(l)=b1+…+bl ,a(l+1) = b1 + … +b(l+1),a数组后续都会受到影响
然后打一个补丁:b(r+1) - c
[基础算法]前缀和与差分_第5张图片

这样子,就保证了不会影响l,r区间内的数,因此得出一维差分的结论

a数组[l,r]区间的每一个数加上一个常数c,只需要让b数组做
b[l] += c;    b[r+1] -= c; 时间复杂度为O(1)

因此只需要O(1)的时间给一个数组加上一个给定的值
假定a数组原本都是0,这样b数组所有也都是0
但是a数组是有值的,因此可以假定进行了n次插入操作,也就是a数组第一个数为[1,1] + a1,第二个数就是[2,2]+a2

#include 
#include 

using namespace std;

const int N = 100010;
int n, m;
int a[N], b[N];

void insert(int l, int r, int c) // [l, r]这段区间+c
{
    // 构造差分数组b
    b[l] += c; // b[l]+c后,a[l]及其之后的所有数都加上了c
    b[r + 1] -= c; // 这样子a[r+1]及其之后的所有数都减去了c
}

int main()
{
    cin >> 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]); // 初始ab数组都是0,b是a的差分数组,但是a数组填入数后,为了保证b仍然是a的差分,所以这里要insert
    }
    
    while (m--)
    {
        int l, r, c;
        cin >> l >> r >> c;
        insert(l, r, c);
    }
    
    for (int i = 1; i <= n; ++i)
    {
        a[i] = a[i - 1] + b[i]; // a数组就是b数组的前缀和
    }
    
    for (int i = 1; i <= n; ++i)
    {
        cout << a[i] << " ";
    }
    
    return 0;
}

2.2 二维差分

[基础算法]前缀和与差分_第6张图片

a数组是b数组的前缀和数组,那么b是a的差分数组
[基础算法]前缀和与差分_第7张图片

#include 

using namespace std;

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()
{
    int n, m, q;
    cin >> 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]); // 构造差分矩阵b
        }
    }
    
    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);
    }
    
    // 求b数组的二维前缀和
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j]; // a数组就是b数组的前缀和,这样子就求出a数组
        }
    }
    
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            printf("%d ", a[i][j]);
        }
        cout << endl;
    }
    return 0;
}

你可能感兴趣的:(算法,算法,c++)