【算法笔记】-前缀和和差分

【算法笔记】-前缀和和差分

问题导入:

给定n个数据,他们的序号从1到n。现在有m次询问,每次给到两个数字x,y,需要输出区间[x,y]的和。

如果我们暴力解决这个问题,时间复杂度为O(n*m)。如果数据规模达到10^5以上,就会超时。

优化思路:对数据预处理,使得能在O(1)时间内实现查询,显然区间[x,y]的和等于[1,y]的和减去[1,x-1]的和。这里我们需要预处理出全部1到i(1<=i<=n)的和。这种处理我们称之为前缀和。

所谓前缀和可以理解为数学里的数列前n项和,是用来求子区间和的一种便捷方法,与暴力解法相比,其时间复杂度低,大大提高了运行效率。

对于以上问题,可以写出如下代码解决:

#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],sum[N];
int main(){
  int n,m,l,r;
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    sum[i]=sum[i-1]+a[i];//预处理前缀和
  }
  for(int i=1;i<=m;i++)
  {
    cin>>l>>r;
    cout<<sum[r]-sum[l-1]<<endl;
  }
  return 0;
}

以上是一维前缀和,如果是二维的呢?

和一维前缀和一样,我们需要构造一个前缀和数组sum[i][j]。利用前缀和的思想:我们需要通过前面已知的前缀和来推出后面未知的前缀和。

红色大框的总和为sum[i][j]。实际上所有临近的线条都应该重叠的,为了方便观察错开了一点。根据幼儿园的知识,显然红色矩形的面积=绿框面积+蓝框面积-黑框面积。

转换成前缀和数组:sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];

查询一个区间的数据和,

假设左上角是(a,b),右下角是(c,d) 那么包含这两个点的矩形中的数据和为:

ans=sum[c][d]-sum[c][b-1]-sum[a-1][d]+sum[a-1][b-1];


#include <iostream>
using namespace std;
int a[1005][1005];
int main()
{
   int n,m;cin>>n>>m;
   for(int i=1;i<=n;i++)
       for(int j=1;j<=n;j++)
           cin>>a[i][j];
   //预处理前缀和数组
   for(int i=1;i<=n;i++)
       for(int j=1;j<=n;j++)
       {
           s[i][j]=s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1];
       }
   //查询
   while(m--){
       int x,y,c,d;cin>>x>>y>>c>>d;
       cout<<s[c][d]-s[x-1][d]-s[c][y-1]+s[x-1][y-1]<<endl;
   }
   return 0;
}

差分

所谓差分可以理解为前缀和的逆运算,就是将数列中每一项分别与前一项做差。

经过简单计算验证,对差分数组求前缀和可以得到原数组,对前缀和数组求差分可以得到原数组。

差分一般用来解决区间同时加一个数的问题。

【题目描述】

输入一个长度为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

根据差分和前缀和的性质,对于差分数组b的下标为l加上c的操作等于对于原数组a的下标为l之后的所有元素加上c的操作,由此可见,若是在一维层面上对于区间元素操作则可以解释为

对于原数组在区间[L,R]每个数加上c相当于在差分数组L位置加上C,在R+1位置减去c。

对于所给的一维数组a[n],我们先要构造其一维差分数组d[n]。按照差分的定义我们很容易就能想到:d[n]=a[n]-a[n-1]

给[L,R]区间加上c:d[L]+=c;d[R+1]-=c;

完整代码如下:


#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],d[N];
int main(){
  int n,m,l,r,c;
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    d[i]=a[i]-a[i-1];//预处理差分数组 
  }
  for(int i=1;i<=m;i++)
  {
    cin>>l>>r>>c;
    d[l]+=c;d[r+1]-=c;
  }
  for(int i=1;i<=n;i++)
  {
    d[i]=d[i-1]+d[i];//求前缀和恢复原数组
    cout<<d[i]<<' ';
  }
  return 0;
}

二维差分问题:

根据前面和二维前缀和和差分的知识,显然:

题目描述

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

输入格式

第一行包含整数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 = 1000+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[x1][y2 + 1] -= c;
  b[x2 + 1][y1] -= 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];
      //预处理差分矩阵 
      b[i][j]=a[i][j]-a[i][j-1]-a[i-1][j]+a[i-1][j-1];
    }
  }
  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];
      cout<<b[i][j]<<' ';
    }
    cout<<endl;
  }
  return 0;
}

posted on   codelucky  阅读(8)  评论(0编辑  收藏  举报

(评论功能已被禁用)
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示