e哥的基础算法第一节

                                                前缀和和差分
            这是基础课算法第一节,主要是讲了,前缀(二维)和 和差分。

那好我们来想想什么是前缀和呢?如果我们去oi wiki和买过一些黑皮书,它们告诉我们的答案都是前缀和类似于高中所学的数列前n项和,高中数列有两个公式很重要,一个是等差数列公式,一个是等比数列公式,当然这与我们所讲的内容关系不大,只是一个联想。

 相对的前缀和也有两个公式,一个相加求和,一个相减求和

    可以看到通过相加来推出相减,而且只需要一次计算,就能得到结果。

前缀和数组的定义

假设有一个数组 arr,它的前缀和数组 prefix 定义为:

  • prefix[0] = arr[0]
  • prefix[1] = arr[0] + arr[1]
  • prefix[2] = arr[0] + arr[1] + arr[2]
  • 以此类推

一般地,prefix[i] 表示原数组 arr 从索引 0 到索引 i 的所有元素的和

我们看一道题

有 N 个的正整数放到数组 A里,现在要求一个新的数组 B,新数组的第 i 个数  是原数组  A第i0到第 i个数的和。

输入:

5

1 2 3 4 5

输出:

1 3 6 10 15

思路:原数组B[0]与A[0]相同,B[i]=B[i-1]+A[i]。

代码如下:

#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int N,A[1000],B[1000];

int main()
{
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
   cin>>N;
   for(int i=0;i<N;i++)cin>>A[i];//先输入A数组的值
    B[0]=A[0];//先表示第1项相同
    for(int i=1;i<N;i++)
    {
    	//前缀和数组第i项=原数组的0到i-1项的和+原数组的第i项
    	B[i]=B[i-1]+A[i];
    }
   for(int i=0;i<N;i++)
     cout<<B[i]<<" ";
   return 0;
}

题目不难,思路比较简单,适合新手。

下面是前缀和的模版,如果实在想不到时可以看看。

题目描述

给定义一个数组a,有q+1次询问,对于每次询问:

给定两个整数l,r求al​+al+1​+...+ar​的结果。

输入描述

第一行一个整数表示样例个数T(1≤T≤10)T(1≤T≤10)。

对于每组样例:

第一行2个整数n(1≤n≤105),q(1≤q≤105),分别表示数组长度和询问次数。

第二行n个整数,表示数组a(−109≤ai≤109)。

接下来q行,每行两个整数l,r(1≤l≤r≤n)询问的区间。

输出描述

对于每组样例,一行一个整数表示答案。

#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
using ll=long long;

const int N=2e5+9;
ll a[N],prefix[N];//prefix[N]表示一个前缀和数组第n个元素

void solve()
{
	 int n,q;cin>>n>>q;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)prefix[i]=prefix[i-1]+a[i];//相加
	while(q--)
	{
		int l,r;cin>>l>>r;
	 cout<<prefix[r]-prefix[l-1]<<'\n';//相减
	}
}



int main()
{
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
   int _;cin>>_;
   while(_--)solve();
   return 0;
}

               以上是一维前缀和数组,现在我们来讲,二维前缀和数组。

                                     二维前缀和的模版图(容斥原理)

       

                        

好,或许大家还有疑问,这个模版跟二维前缀和数组有什么关系?相信看到这里的算法内容的同学应该知道二维数组是什么,相对应的二维前缀和数组,就是在二维数组上面进行求和,那怎么求和呢?

模版上,我们看到的是如果要求prefix[3][3],那就是先加上i-1和j-1的区间,然后再减去i-1和j-1的区间,这是为什么呢?因为这个区间我们已经算了两次了,所以要再减一次,这样才没有重复计算,最后再加上a[i][j]就行了。

                  如果题目中有坐标轴的问题,下列是分析模版(请一定记住)

         

   好,既然你已经知道了二维前缀和数组,那就来做道题吧!

给定一个n行m列的整数矩阵。

有q个询问,每个询问格式为:x1,y1,x2,y2,表示一个子矩阵的左上角和右下角的坐标。

对于每个询问,请回答子矩阵的所有数之和。

输入格式

第一行包括三个整数n,m,q(1≤n,m≤103,1≤q≤105)。

接下来n行,每行包括mm个整数,表示整数矩阵(每个整数的取值范围为[1,105])。

接下来q行,每行包括四个整数x1,y1,x2,y2x1​,y1​,x2​,y2​(1≤x1≤x2≤n1≤x1​≤x2​≤n,1≤y1≤y2≤m1≤y1​≤y2​≤m),表示一个询问的左上角、右下角坐标。

输出格式

共q行,第i(1≤i≤q)行输出第i个询问的结果。

样例输入1:

7 3 2

3 5 1

6 2 4

7 9 10

4 3 6

3 9 9

6 10 1

9 10 4 

2 2 7 3

2 1 4 2

样例输出1

77

31

这题看似简单,实则一点也不难

上代码(建议不要学我,我是懒得打,记得自己先想,想不出来再看):

#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
using ll=long long;

const int N=1e3+9;
ll a[N][N],p[N][N];

int main()
{
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
 ll 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];
  //计算数组
  for(int i=1;i<=n;i++)
  for(int j=1;j<=m;j++)
  p[i][j]=p[i][j-1]+p[i-1][j]-p[i-1][j-1]+a[i][j];
    while(q--)
    {
    	int x_1,y_1,x_2,y_2;cin>>x_1>>y_1>>x_2>>y_2; 
cout<<p[x_2][y_2]-p[x_1-1][y_2]-p[x_2][y_1-1]+p[x_1-1][y_1-1]<<'\n';
    }
   return 0;
}

说实话我也在想为什么x1,x2,y1,y2要用下标才能表示,CP上只有这样才能运行。

                                                         差分数组的思想和模版图

    先上差分模版,看几分钟。

看到这个图片左边这个部分能想到什么呢?是不是很像裂项相消?

通过前n项和相加,我们能看到j开始到i这个区间相加最后得到的结果就是ai,上面的di就是diff(差分数组的表达方式)di=ai-ai-1,比如数组arr[1,2,3],那么diff[1,1,1];这个好理解,右边这个部分代表的是diff区间的加减,简单来说就是在一段差分数组的子区域内进行加减,然后再进行前缀和得到更新后的数组。

那差分数组的定义是什么呢?

差分数组的定义

  给定一个数组 arr 和一个差分数组 diff,我们可以通过以下方式定义差分数组:

  • diff[i] 表示 arr[i] 与 arr[i-1] 之间的差值,即 diff[i] = arr[i] - arr[i-1](对于 i = 0,我们通常定义 diff[0] = arr[0]

步骤如下:

  1. 初始化差分数组:首先,需要从原数组 arr 生成差分数组 diff

  2. 区间更新:要在 arr 的某个区间 [l, r] 上进行加 x 的操作,可以通过差分数组 diff 来实现:

    • 增加 x 到 diff[l]
    • 减去 x 从 diff[r + 1](如果 r + 1 在数组范围内)

     

    3.恢复原数组:在所有更新完成后,可以通过差分数组恢复原数组 arr

  • 对 diff 数组进行前缀和操作,得到更新后的 arr

             

                   好了,你已经了解差分数组了,来做个题

给定一个长度为n的数组a,和两个整数p,q。

先进行p次区间加操作:将区间[l,r]的数字都加上x。

再进行q次区间查询操作:求出[l,r]的数字之和。

对于每次区间查询操作,输出结果。

输入描述

第一行三个整数n,p,q。(1≤n≤105,0≤p≤105,0≤105≤q)

第二行n个整数表示数组a。(−109≤ai≤109)

接下来p行,每行三个整数l,r,x。(1≤l≤r≤n,−109≤x≤109)

接下来q行,每行两个整数l,r。(1≤l≤r≤n)

输出描述

对于每次区间查询操作,输出结果。

输入样例1

5 1 2

1 1 1 2 2

1 4 2

1 3

1 5

输出样例1

9

15

跟上面一题一样,先别看想一下再做

#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e3+9;

ll a[N],diff[N],prefix[N];

int main()
{
   ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll n,p,q;
    cin>>n>>p>>q;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)diff[i]=a[i]-a[i-1];
    
    while(p--)
    {
    	ll l,r,x;cin>>l>>r>>x;
    	diff[l]+=x;diff[r+1]-=x;
    	
    }
    for(int i=1;i<=n;i++)a[i]=a[i-1]+diff[i];//更新后的原数组=原数组i-1+diff[i]
    for(int i=1;i<=n;i++)prefix[i]=prefix[i-1]+a[i];//用前缀和进行累加起来
    
    while(q--)
    {
    	ll l,r;
    	cin>>l>>r;
    	cout<<prefix[r]-prefix[l-1]<<'\n';
    }
   return 0;
}

OK, 我们还剩最后一个内容请继续坚持

先送一段话:“there must be a beginning of every great matter,but the continuing unto the end until it be thoroughly finished yields the true glory”

                                然后开始我们二维差分数组的学习!

先上二位差分模版图片!是不是和二维前缀和数组很相似?

我们看到图片二维差分是为了在原矩阵上绿色的方框内+X,那做法呢?

与二维前缀和数组相似,还是先减后加,把重复减的部分记得加一次,就能得到我们想要的区间了

那二维差分数组的定义是什么呢 ?

二维差分数组的定义

1.二维差分数组的初始化

  1. 给定一个 m x n 的矩阵 matrix,我们定义一个 m x n 的差分数组 diff。(二维差分数组用于高效地处理矩阵中区域的加法操作)

  2. 差分数组的更新

    • diff[row1][col1] += x
    • diff[row1][col2 + 1] -= x (如果 col2 + 1 在范围内)
    • diff[row2 + 1][col1] -= x (如果 row2 + 1 在范围内)
    • diff[row2 + 1][col2 + 1] += x (如果 row2 + 1 和 col2 + 1 在范围内)
    • 对于要在矩阵中的某个子矩形区域 [row1, col1][row2, col2] 进行加 x 的操作,可以通过二维差分数组 diff 实现

3恢复原矩阵更新完成后,需要恢复原矩阵 matrix。具体步骤如下

  1. 创建一个 m x n 的矩阵 matrix,用来存储最终的结果
  2. 使用二维前缀和算法来计算每个位置的实际值。

                                                           好,我们来做最后一题

题目描述

给定一个n行m列的整数矩阵。

有q个操作,每个操作格式为:x1,y1,x2,y2,c ,其中(x1,y1)(x1​,y1​)、(x2,y2)(x2​,y2​)分别表示一个子矩阵的左上角和右下角的坐标,每个操作将对应的子矩阵的每个元素加上c。

请输出进行完所有操作后的矩阵。

输入描述

第一行包括三个整数n,m,q(1≤n,m≤103,1≤q≤105)。

输出描述

共n行,每行包括m个整数,表示进行完所有操作后的矩阵。

输入样例1

4 3 3

1 5 1

3 3 2

5 3 4

4 4 2

1 2 1 2 2

2 1 2 3 2

4 2 4 3 1

输出样例1

1 7 1

5 5 4

5 3 4

4 5 3

最后一题了,听我一句劝,还是不要再抄了,不然对自己提升的效果不大的

#include<bits/stdc++.h>
using namespace std;
using ll= long long;
const ll N=1e3+9;
ll a[N][N],p[N][N],d[N][N];

int main()
{
 ios::sync_with_stdio(0),cout.tie(0),cin.tie(0);
        ll 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];
        //计算数组
        
        for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
        	d[i][j]+=a[i][j];
        	d[i+1][j]-=a[i][j];
        	d[i][j+1]-=a[i][j];
        	d[i+1][j+1]+=a[i][j];
        	
        }
        //进行修改操作
         
            while(q--)
            {
            	int x1,y1,x2,y2,c;cin>>x1>>y1>>x2>>y2>>c;
            	d[x1][y1]+=c;
            	d[x1][y2+1]-=c;
            	d[x2+1][y1]-=c;
            	d[x2+1][y2+1]+=c; 
            
            }
        
        
        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]+d[i][j];
           cout<<a[i][j]<<' ';
           }
           cout<<'\n';
          }  
    return 0;

}

 好了,总结:差分数组(diff)和前缀(prefix)和都是在为原数组a[i]进行修改把它当成一个高中数列求前n项和的问题

这是第一次在csdn上发博客,如有问题还请见谅。

  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值