首页 > 其他分享 >差分

差分

时间:2024-04-07 11:24:32浏览次数:22  
标签:前缀 int 差分 数组 y1 我们

前言

学习差分前一定要先学习前缀和,因为差分就是前缀和的一个逆运算(有点像微分和积分),所以只有先搞清楚前缀和才能明白差分

点这里补习前缀和

这里同样也是从一维和二维两个角度去分析差分这个算法

正文

我们要先理清差分的含义:注意关系,这里跟前缀和里举的例子有差别,b的前缀和数组是a(为了便于理解)

  • 我们已经知道如果b数组的前缀和数组为a,那么a[n] = b[1] +b[2] +....+ b[n]。我们称a数组是b的前缀和

  • 那么现在我们反过来,我们就称b数组是a数组的差分

    image-20230304182224245

那么现在已知a数组,要求你构造一个b数组,使a数组是b数组的前缀和

其实对于一维来说构造相当简单,我们让b[n] = a[n] - a[n-1]即可。

但是现在我们不使用这种思路。

我们只需要先明白一点:我们通过a的差分数组b,可以得到a数组本身。其实也可以理解为一种映射。

一维差分

例子引入

这里给出一个差分数组的实际使用案例

我们有a数组和他的差分数组b,现在我们需要给a数组的[l , r]区间内的数都加c。正常情况下我们需要进行一次遍历,给区间每一个数都加上c,时间复杂度为O(n)。

但是上面我们说过,我们可以通过差分数组得到原数组,所以如果我们改动了差分数组,就相当于对原数组做了处理:尽管实际上a数组中的元素都没有改变,但是以后我们都是通过差分数组来得到原数组,而不是通过a来得到原数组(相当于我们通过a创建完他的差分数组后,a就可以不要了,因为b可以映射出a)。

所以现在我们的操作就是:b[l] += cb[r+1] -=c

  • 我们映射a数组是通过a[n] = b[1] +b[2] +....+ b[n]

  • 在映射a数组[1, l-1]时,上面两个位置都没有用到,所以a数组在[1, l-1]区间的值都没有改变

  • 在映射a数组[l, r]时,我们的加法里包含b[l]而不包含b[r],由于b[l]加了c,所以这一段中的a元素也会随着+c

  • 在映射a数组[r+1, .....]时,我们两个位置都用到了,而+=c-=c的影响会抵消掉,所以后面跟第一段区间一样,元素都没有受到影响

  • 综上,只有[l, r]区间中的a数组元素+c,这便达成了我们的目的。时间复杂度为O(1)

我们从上述的例子中得到了更新的方法,便是b[l] += cb[r+1] -= c

以更新代构造

我们可以直接通过这种更新方式作为构造差分数组的方法

尽管我们知道a数组里有数据,但是我们现在就假定里面的数据全为0,那么显然,现在他的差分数组里面也全是0,然后假设我们现在开始从第1为更新a数组

由上面的例子我们知道,这种更新可以通过b来完成

  • 第一次,我们让a[1] = a1(放入第一个数据),其实就相当于上面例子中的让a数组[1, 1]区间内的所有数都+a1
  • 那么与上个例子一样,我们要做的就是b[1] += a1b[2] -= a1
  • 第二次让a[2] = a2,即b[2] += a2b[3] -= a3
  • 推理下去,我们的构造方式就很明显了,遍历a数组,进行b[n] += a[n]b[n+1] -= a[n]的步骤。最终就能构造出差分数组b

代码

image-20230304184954094

#include <iostream> using namespace std; const int N = 100010; int n, m; int a[N], b[N]; // 进行我们的模拟更新,借此构造出差分数组b 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]); while (m -- ) { int l, r, c; scanf("%d%d%d", &l, &r, &c); insert(l, r, c); } // 让b数组变成a数组,注意a是b的前缀和 for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1]; for (int i = 1; i <= n; i ++ ) printf("%d ", b[i]); return 0; }

二维差分

这里的思路与上面的一样,都是通过更新方法代替构造方法。

所以做重要的就是要理清更新方法

现在假如我们让(x , y) += c,与上面的分析同理,我们不难发现他的作用是将这个点与右下角形成的矩阵中的所有点的数值加c,如下图黄色区域所示

image-20230305194447027

与上面考法相同,这里我们会给出两个坐标,我们需要让这两个坐标形成的矩阵中的所有点数值增加

那么根据更新的方法,我们就不难想到这种更新的策略

image-20230305195201337

  • 我们先用(x1 , y1)更新,让他下面的所有都增加。
  • 然后借助两个紫色的点更新减法,让他们下面的区域都减少。
  • 由于两个区域重合,所以再更新(x2+1 , y2+1)增加一份来补全多减的那部分(还是要注意,这是数组,不是连续的,而且我们要包含边界
  • 总结为公式:(x1 , y1) += c(x1 , y2+1) -= c(x2+1 , y1) -= c(x2+1 , y2+1) += c

代码

image-20230305195731750

#include<iostream> using namespace std; const int N=1e3+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[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++){ cin>> 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]); } } int x1,y1,x2,y2,c; while(q--){ cin>> x1 >> y1 >> x2 >> y2 >> c; insert(x1,y1,x2,y2,c); } // 让b数组变成a数组,注意a是b的前缀和 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; } }

标签:前缀,int,差分,数组,y1,我们
From: https://www.cnblogs.com/YJH6994/p/18118693

相关文章

  • 差分约束
    前言考虑单源最短路的一个性质:在有向图中,若存在边\(u\tov\),边权为\(w\),则\(\mathit{dis}_u+w\ge\mathit{dis}_v\)。正确性是显然的,因为如果反之\(\mathit{dis}_u+w<\mathit{dis}_v\),我们就可以令\(\mathit{dis}_v\gets\mathit{dis}_u+w\),原先的就不是最短路了,与题设矛盾......
  • P5960 【模板】差分约束
    原题链接题解我一直苦苦思考为什么要建边,现在我明白了,如果令\(x_i\)代表离源点的最短路径长度的话,建边之后,\(x_i-x_j<=y_k\)一定成立只有当出现负环的时候说明条件出现了矛盾太神了code#include<bits/stdc++.h>usingnamespacestd;queue<int>q;intin_q[5005]={0};......
  • 一维差分算法
    目录背景:一维插分应用场景一维差分法原理解释背景:在刷javaA组蓝桥本真题时,碰到了一个用二维差分方法的解题思路,意识到自己差分不熟悉,就想先学习一下一维差分。一维插分应用场景题目给出一个一维数组,并多次修改某一区间的值。例如:一个数组长度为8,初始均为0,输入数......
  • 差分数组
    定义对于数组A[n],它的差分数组为:\[diff[i]=\begin{cases}A[i],&i==0\\A[i]-A[i-1],&0<i<n\end{cases}\]显然,通过差分数组diff[n],可以求得A[n]中的某一具体元素:\[A[i]=\begin{cases}diff[i],&i==0\\diff[i]+A[i-1],&0<i<n\end{cases}\]应用数组A[n]......
  • 前缀和与差分数组
    目录一维前缀和二维前缀和差分数组差分数组反推出原始数组差分数组模板前缀和应用场景:原始数组不会被修改的情况下,频繁查询某个区间的累加和。eg:计算数组下标2到5的数组和,原本用for循环,有了前缀和直接用5的前缀和减去2的前缀和得到答案,可以将O(n)的复杂度降为O(1)一维前缀和......
  • 一本通差分约束入门题
    最关键的就是找好所有的要满足的不等式条件,注意隐含的条件还有一点就是注意没有源点建立源点#2436. 「SCOI2011」糖果#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;usingpii=pair<int,int>;#defineintlonglongconstintN=5e5+10,M......
  • 【MATLAB源码-第15期】基于matlab的MSK的理论误码率与实际误码率BER对比仿真,采用差分
    操作环境:MATLAB2022a1、算法描述在数字调制中,最小频移键控(Minimum-ShiftKeying,缩写:MSK)是一种连续相位调制的频移键控方式,在1950年代末和1960年代产生。[1]与偏移四相相移键控(OQPSK)类似,MSK同样将正交路基带信号相对于同相路基带信号延时符号间隔的一半,从而消除了已调信号......
  • 一 503. 借教室 (差分|二分)
    503.借教室(差分|二分)importjava.util.*;publicclassMain{privatestaticintn,m;privatestaticint[]rooms;privatestaticint[][]orders;privatestaticbooleancheck(intmid){long[]diff=newlong[n+2];f......
  • 时序分析:基础知识整理(三)差分转单端的约束等
    之后的都只有我个人能看,想看的请支持单刀大佬。主时钟约束主时钟约束,就是我们对主时钟(PrimaryClock)的时钟周期进行约束(告诉综合工具布局布线的标准),这个约束是我们用的最多的约束了,也是最重要的约束。主时钟必须与一个网表对象相连,该对象代表了所有时钟边沿的开始点,并且在时钟......
  • 差分约束
    (例)layout传送门题目描述当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。FJ有N(2<=N<=1000)头奶牛,编号从1到N,沿一条直线站着等候喂食。奶牛排在队伍中的顺序和它们的编号是相同的。因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上。即使说,如果我们想象奶牛是站在一......