本篇主要介绍一下前缀和&差分
前缀和&差分
参考文章:前缀和
& 差分 - OI Wiki 一篇带你速通差分算法(C/C++)-CSDN博客
前缀和
I.普通前缀和
介绍:前缀和可以简单理解为「数列的前 𝑛 项的和」,是一种重要的预处理方式.主要用于快速求区间和
对于长度为 𝑛 的序列 {𝑎𝑖} ,如果要多次查询区间 [𝑙,𝑟] 中序列数字的和,就可以考虑使用前缀和.序列的前缀和就是
$$
S_i=\sum_{j=1}^{i}a_j
$$ 它可以通过递推关系式 S 0 = 0, S i = S i − 1 + a i
逐项计算得到.要询问区间 [𝑙,𝑟] 内的序列的和,只需要计算差值 S ([l , r ]) = S r − S l − 1
就这样,通过 𝑂(𝑛) 时间预处理,能够将单次查询区间和的复杂度降低到 𝑂(1) .
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <bits/stdc++.h> using namespace std;#define int long long signed main () { int n; cin>>n; vector<int >a (n+1 ); vector<int >pre (n); pre[0 ]=0 ; for (int i=1 ;i<=n;i++) { pre[i]=pre[i-1 ]+a[i]; } }
II.二维前缀和
当题目出现矩阵时,说明就涉及到了二维前缀和,其模型可以抽象为矩阵
给定大小为 𝑚 ×𝑛 的二维数组 𝐴 ,要求出其前缀和 𝑆 .那么,𝑆 同样是大小为 𝑚 ×𝑛 的二维数组,且 $$
S_{i,j}=\sum_{e=1}^{i}\sum_{f=1}^{j}A_{e,f}
$$ 类比一维的情形,𝑆𝑖,𝑗 应该可以基于 𝑆𝑖−1,𝑗 或 𝑆𝑖,𝑗−1
计算,从而避免重复计算前面若干项的和.但是,如果直接将 𝑆𝑖−1,𝑗 和 𝑆𝑖,𝑗−1 相加,再加上 𝐴𝑖,𝑗 ,会导致重复计算 𝑆𝑖−1,𝑗−1
这一重叠部分的前缀和,所以还需要再将这部分减掉.这就是 容斥原理 .由此得到如下递推关系:
S i , j = S i − 1, j + S i , j − 1 + A i , j − S i − 1, j − 1
实现时,直接遍历 (𝑖,𝑗) 求和即可.
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int n; cin>>n; int b[n+1 ][n+1 ]; int presum[n+1 ][n+1 ]; for (int j=0 ;j<=n;j++) { b[0 ][j]=0 ; } for (int i=0 ;i<=n;i++) { b[i][0 ]=0 ; } for (int i=1 ;i<=n;i++) { for (int j=1 ;j<=n;j++) { cin>>b[i][j]; presum[i][j]=b[i][j]+presum[i-1 ][j]+presum[i][j-1 ]-presum[i-1 ][j-1 ]; } }
差分
差分算法的核心思想是利用已有的数据序列,通过计算相邻元素之间的差值来生成一个新的序列。对于一个给定的序列
a=[a1,a2,…,an],其差分序列 d
可以表示为:d[i]=a[i]−a[i-1]。差分数组长度一般为原定序列长度+1,即:len=n+1。其中,d[0]
通常被定义为0,或者根据具体应用场景进行特殊处理。
一维差分在修改区间时效率非常高,时间复杂度可以达到
O(1),我们通过对差分数组的修改以达到修改原数组的目的,那么如何修改差分数组,比如在数组a的[l,r]区间上的数统一+c,转化为差分数组为d[l]+c,d[r+1]-c,这样我们再利用前缀和还原便可以得到原数组修改后的值。在还原时,只需要将前i位差分数组相加便可以得到原数组,比如还原第三位,a[3]=d[1]+d[2]+d[3],便可以还原其值,其还原的第n+1位一定是0。
具体的还是看第二篇博客吧
例题一.差分+二分以及一点小优化
[P1083 NOIP 2012
提高组] 借教室 - 洛谷
这题的话鼠鼠一开始只打算用差分,结果很明显超时了,所以鼠鼠用二分优化,假设在第mid天会超过,慢慢二分搜索,结果还是会t,最后就加了一点小优化,记录下前一次的差分结果,在此基础上进行计算,AC了。
下面是代码
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <bits/stdc++.h> using namespace std;#define int long long int check (int mid,vector<int >&dif,vector<vector<int >>h,int n,int &last) { if (last<=mid) { int d,s,t; for (int i=last+1 ;i<=mid;i++) { d=h[i][0 ]; s=h[i][1 ]; t=h[i][2 ]; dif[s]-=d; dif[t+1 ]+=d; } } else { int d,s,t; for (int i=last;i>mid;i--) { d=h[i][0 ]; s=h[i][1 ]; t=h[i][2 ]; dif[s]+=d; dif[t+1 ]-=d; } } int temp=0 ; int flag=1 ; for (int i=1 ;i<=n;i++) { temp+=dif[i]; if (temp<0 ) { flag=0 ; break ; } } last=mid; return flag; } signed main () { int n,m; cin>>n>>m; vector<int >r (n+1 ); vector<int >dif (n+2 ); dif[0 ]=0 ; for (int i=1 ;i<=n;i++) { cin>>r[i]; } for (int i=2 ;i<=n;i++) { dif[i]=r[i]-r[i-1 ]; } dif[1 ]=r[1 ]; dif[n+1 ]=0 ; int flag=0 ; vector<vector<int >>h (m+1 ); for (int i=1 ;i<=m;i++) { int d,s,t; cin>>d>>s>>t; h[i].push_back (d); h[i].push_back (s); h[i].push_back (t); } int le=1 ; int ri=m; int mid=0 ; int last=0 ; while (le<=ri) { mid=(le+ri)/2 ; if (check (mid,dif,h,n,last)) { le=mid+1 ; } else { flag=mid; ri=mid-1 ; } } if (flag) { cout<<-1 <<endl; cout<<flag<<endl; } else { cout<<0 <<endl; } }