详解一维二维前缀和

@ [toc]前缀和

一、一维前缀和

1.前缀和是啥

前缀和其实就是用一个数组S存下数组a的前缀的和,这样话方便以后的查找,提高查找的速度。

2.一维前缀的时间复杂度

因为是遍历一遍数组a就可以得到我们的前缀和数组,所以前缀和的时间复杂度是O(n)。
如果是m次查找的话,我们就可以做到O(n+m)的时间复杂度。

3.一维前缀和公式的推导

一维的前缀和比较简单,这样的话我们先推导一下一维的前缀和的公式。

为了方便的话,我们一般在用前缀和的时候数组从下标为1的地方开始读入。这样是为了防止数组越界,我们可以这样考虑,当前我们已经该算下标为i的前缀和,因此我们这时肯定已经算过了下标为i-1的前缀和,这是我们这时只需要用下标为i-1的前缀和加上当前的下标为i的那个值,是不是就可以得到下标为i的前缀和。

于是我们可以很容易的得到一维前缀和的公式 S [ i ] = S [ i − 1 ] + a [ i ] S[i]=S[i-1]+a[i] S[i]=S[i1]+a[i]

for(int i = 1; i <= n; i ++)
  s[i] = S[i - 1] + a[i];

通过这样的一段代码就可以求出a数组的前缀和数组S,那我们知道了这个概念的话怎么用呢,我们接下来就看一道例题

4.一维前缀和的例题

AcWing前缀和例题
思路:就是一个非常裸的前缀和,然后进行m次查询即可,如果我们每次都进行遍历求和的话就会超时。
参考代码

#include 
#include 
#include 
using namespace std;

const int N = 1e5 + 10;
int a[N],S[N];

int main()
{
     ios::sync_with_stdio(false);
     cin.tie(0);
     cout.tie(0);
     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]=S[i-1]+a[i];
     while(m--)
     {
          int l,r;
          cin>>l>>r;
          cout<<S[r]-S[l-1]<<endl;
     }
     return 0;
}

二、二维前缀和

1.二维前缀和的时间复杂度

二维前缀和的时间复杂度其实可以类比一维的时间复杂度,就是把这个二维数组遍历一遍,这样的话就是O(n*m)的时间复杂度

2.二维前缀和公式的推导

二维的前缀和就比一维的麻烦一些,我们没办法直接求出我们需要的所以我们只能先算出一个然后再减去重复的部分,最后我们可以得到 s [ i ] [ j ] = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] − s [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j] s[i][j]=s[i1][j]+s[i][j1]s[i1][j1]+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];

3.二维前缀和的子矩阵的查询

子矩阵也不是直接就可以查出来我们也只能用公式进行计算和上面求前缀和的过程差不多 s [ x 2 ] [ y 2 ] − s [ x 1 − 1 ] [ y 2 ] − s [ x 2 ] [ y 1 − 1 ] + s [ x 1 − 1 ] [ y 1 − 1 ] s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1] s[x2][y2]s[x11][y2]s[x2][y11]+s[x11][y11]

4.二维前缀和的例题

AcWing二维前缀和的例题
思路:就是简单的二维前缀和,正常的直接加的话会超时

#include 
using namespace std;

const int N = 1e3 + 10;
int a[N][N], s[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", &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, x2, y1, y2;
          scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
          printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
     }
     return 0;
}

算法竞赛进阶指南-激光炸弹

思路:这个题的话我们可以,先用前缀和去处理一下,所有S[i][j]当前的价值,然后我们可以,用O(n*m)的时间复杂度去求一下这个二维矩阵的前缀和,然后我们就可以在下面直接遍历一下所有的边长是R的正方形,然后用O(1)的时间复杂度去查找就行,并同时维护价值最大的那个即可。(此题还有一个小点就是如果开两个数组去求二维前缀和的话会内存超限,我们就要转化成一个二维数组求)。

#include 
#include 
#include 
using namespace std;

const int N = 5010;
int S[N][N];

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int N, R;
    cin >> N >> R;
    int n, m;
    R = min(R, 5001);//最的话就是5000所以超过就和5001去一个min值
    n = m = 5001;
    while (N--)
    {
        int x, y, k;
        cin >> x >> y >> k;
        x++, y++;
        S[x][y] += k;//x和y不止出现一次所以我们要 +=
    }
    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]

    int res = 0;
    for (int i = R; i <= n; i++)
        for (int j = R; j <= m; j++)
            res = max(res, S[i][j] - S[i - R][j] - S[i][j - R] + S[i - R][j - R]);//因为此题说在边上的话就不算了
            //其实我们在这里的话求的是边长是R-1的正方形,要求边长为R的话,我们就要i-R-1才行

    cout << res << endl;
    return 0;
}

你可能感兴趣的:(算法竞赛进阶指南,算法,c++,数据结构)