前缀和(一维与二维) 差分

一维前缀和

很简单,可以联想等差数列求每一段和的公式 Sij = Sj - Si

一维的前缀和的初始化就是 S[i] = S[i-1] + a[i] 前缀和数组从1开始初始化

所以想要求 x -> y 段和 公式就是 res = S[y] - S[x-1]

代码

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

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

int main()
{
    int m, n;

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

    system("pause");
    return 0;
}

二维前缀和

典型例题就是求矩阵中子矩阵的和

S[i][j] 就是从矩阵左上角到右下角i j的子矩阵和

根据几何:看图

初始化的公式就是: s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];

求子矩阵公式: res = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]

这两个公式可以互推

代码:

#include <iostream>

using namespace std;

const int N = 1010;
int n, m, q;
int a[N][N], s[N][N];

int main()
{
    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++)
        {
            s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1];
        }
    }

    while(q--)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int res = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
        cout << res << endl;
    }
    system("pause");
    return 0;
}

关于坐标为什么要减一

这里的意思是我们直接把小格作为坐标而不是交点,看图,求图中绿色子矩阵的和

一维差分

差分就是求前缀和的逆运算,假设对数组b是a的差分数组 那么a就是b的前缀和数组

作用:主要作用就是想要在a数组里给定 [l, r] 区间里各加一个数,我们可以用一个循环,用O(n)完成 但是有了差分后可以用O(1)完成这个工作

假设b是a的差分数组 那么 b[i]+c ,那么 a[i]~a[n] 全部都会加c,因为a相当于b的前缀和数组 所以利用这个性质 我们只需要操作两个数就可以达到这个作用

b[l] += c ;

b[r] -= c ;

看图:

对于b数组的构造我们不用去管 我们可以假设一开始a b 两个数组都为0,那么输入\(a[i]\)的过程我们就可以认为是对b做了n次插入操作,第一次就是\([1, 1] + a[1]\), 第二次\([2, 2] + a[2]\), ....那么每往a中插一个数字 就相当于往b中差一个数字 所以我们只需遍历一遍a 插入到b中即可 所以我们的 insert 可以通用

最后再对b求一遍前缀和 就是我们的答案

代码:

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

const int N = 100010;
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]);  //第一步就是假设两个a b数组都为0,然后可以认为输入的每个数据都是现插入的   
                                                     //这样就可以直接构造原来数组的差分数组
    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]);

    system("pause");
    return 0;
}

二维差分

构造:类比于一维

核心代码:

    b[x1][y1] += c;
    b[x2+1][y1] -= c;
    b[x1][y2+1] -= c;
    b[x2+1][y2+1] += c;

看图:

代码:

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

const int N = 1010;
int n, m, q;
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;
        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++)
        {
            cout << b[i][j] << " ";
        }
        cout << endl;
    }
    system("pause");
    return 0;
}
posted @ 2020-07-09 18:02  Xxaj5  阅读(151)  评论(0编辑  收藏  举报