前缀和与差分
前缀和与差分:
前缀和:即指某个序列的前n项和 差分:即前缀和的逆过程
1.一维前缀和与二维前缀和:
一维前缀和:(1).预处理(O(n)),s[i]统计a[1]--a[i]的和
(2).查询(O(1)):要查从l--r的值的和即等于s[r]-s[l-1]
即:S[i]=a[1]+a[2]+...+a[i] a[l]+a[l+1]+...+a[r]=s[r]-s[l-1]
二维前缀和:(1).预处理复杂度O(n*m) (2).查询:O(1)
s[i][j]表示第i行第j个元素的左上角所有元素的和
由图知:以(x1,y1)为左上角,(x2,y2)为右下角的所有元素的和为:
s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]
一维前缀和的应用:
对一长度为n的序列,每次询问给定一个l和r,求其从l到r的元素和
分析:对于本题如果采用传统的方式,每次询问就从l到r进行一次求和,则时间的复杂度为O(n*m),如果使
用前缀和的知识,就可以先进行一遍预处理O(n),接下来的m次询问的时间复杂度为O(1)*m,所以总的时间复
杂度为O(n+m)
代码如下:
求前缀和的运算
int sum[maxn],a[maxn]; for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
查询操作:
int l,r,ans; scanf("%d%d",&l,&r); ans=sum[r]-sum[l-1];//注意此处不是sum[r]-sum[l]!
二维前缀和的应用:
输入一个n行m列的矩阵,多次询问给定两个点的坐标(x1,y1),(x2,y2),表示一个子矩阵的左上角和右下角
求这个子矩阵的所有元素的和
代码如下:
求前缀和(预处理):
int a[maxn][maxn],s[maxn][maxn]; 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]+a[i][j]-s[i-1][j-1];
每次询问处理:
int x1,y1,x2,y2,ans; while (q--) { scanf("%d%d%d%d",&x1,&y1,&x2,&y2); ans=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]; printf("%d",ans); }
2.一维差分与二维差分
一维差分:(1).差分数组:给定一个原数组a[1],a[2],...,a[n];在构造一个新的数组b[1],b[2],...,b[n]
使得a[i]=b[1]+b[2]+...+b[i],所以已知a[i]求b[i]的过程就是一个差分的过程,而已知b[i]求a[i]的过
程则是前缀和的过程,所以差分是前缀和的逆运算
差分的方法:a[0]=0,b[1]=a[1]-a[0],b[2]=a[2]-a[1]...,b[n]=a[n]-a[n-1]
一维差分的应用:1.若对m次询问l,r,要使a[l]--a[r]的所有数都加上c,如果每次都按照操作从l-r去加上
某一个数,那么时间的复杂度为O(n*m),但使用一个差分数组b[i]后,我们只需要将b[l]+c,那么l以后的所
有a[i]都会加上c,所以我们只需要在将r以后的数减掉一个c即可,即将b[r+1]-c,所以操作的时间复杂度被
降为O(n+2*m),时间复杂度大幅度降低
代码实现:
求差分的运算:
int a[maxn],b[maxn]; for (int i=1;i<=n;i++) { scanf("%d",&a[i]); b[i]=a[i]-a[i-1]; }
询问处理操作:
while (q--) { scanf("%d%d%d",&l,&r,&c); b[l]+=c; b[r+1]-=c; }
二维差分:二维差分不好直接构造差分数组,我们可用差分数组的特点去构造一些数,然后得到需要的差
分数组,所以如果要使以(x1,y1)为左上角的点,(x2,y2)为右下角的点的矩阵全部加c,仅需对假定构造好
的差分数组b进行以下操作即可:
void insert(int x1,int x2,int y1,int y2,int c) { b[x1][y1]+=c; b[x1][y2+1]-=c; b[x2+1][y1]-=c; b[x2+1][y2+1]+=c; }
在构造一个二重循环每次都执行一遍insert就可以构造出所需要的差分数组
for (int i=1;i<=n;i++) { for (int j=1;j<=m;j++) { insert(i,j,i,j,a[i][j]); } }
当然二维差分数组也可以直接进行构造(其与二维前缀和数组的构造基本类似):
b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];
二维前缀和预处理,dp分治为水平和竖直处理计算处理的例题:
题目:They say "years are like dominoes, tumbling one after the other". But would a year fit into a grid? I don't think so.
Limak is a little polar bear who loves to play. He has recently got a rectangular grid with h rows and w columns. Each cell is a square, either empty (denoted by '.') or forbidden (denoted by '#'). Rows are numbered 1 through h from top to bottom. Columns are numbered 1 through w from left to right.
Also, Limak has a single domino. He wants to put it somewhere in a grid. A domino will occupy exactly two adjacent cells, located either in one row or in one column. Both adjacent cells must be empty and must be inside a grid.
Limak needs more fun and thus he is going to consider some queries. In each query he chooses some rectangle and wonders, how many way are there to put a single domino inside of the chosen rectangle?
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <vector> 6 #define rep(i,l,r) for (int i = l; i <= r; ++i) 7 using namespace std; 8 typedef long long ll; 9 const int MAXN = 505; 10 const int INF = 1e9 + 7; 11 int h,w; 12 char ch; 13 bool grid[MAXN][MAXN]; 14 int dp_hor[MAXN][MAXN];//horizontal 15 int dp_ver[MAXN][MAXN];//vertical 16 int q; 17 int r1,c1,r2,c2; 18 int ans; 19 int main() 20 { 21 scanf("%d%d",&h,&w); 22 rep(i,1,h) 23 rep(j,1,w) 24 { 25 scanf(" %c",&ch); 26 if (ch == '.') 27 grid[i][j] = true; 28 } 29 //前缀和预处理,分别获得每个格子的水平与竖直方案数和 30 rep(i,1,h) 31 { 32 rep(j,1,w) 33 { 34 dp_hor[i][j] = dp_hor[i - 1][j] + dp_hor[i][j - 1] - dp_hor[i - 1][j - 1]; 35 dp_ver[i][j] = dp_ver[i - 1][j] + dp_ver[i][j - 1] - dp_ver[i - 1][j - 1]; 36 if (!grid[i][j]) 37 continue; 38 if (grid[i][j - 1]) 39 ++dp_hor[i][j]; 40 if (grid[i - 1][j]) 41 ++dp_ver[i][j]; 42 } 43 } 44 scanf("%d",&q); 45 rep(i,1,q) 46 { 47 ans = 0; 48 scanf("%d%d%d%d",&r1,&c1,&r2,&c2); 49 //计算从(r1,c1)到(r2,c2)的贡献 50 //会多计算r1这一行的竖直贡献 51 ++r1; 52 ans += dp_ver[r2][c2] - dp_ver[r2][c1 - 1] - dp_ver[r1 - 1][c2] + dp_ver[r1 - 1][c1 - 1]; 53 --r1; 54 //会多计算c1这一列的水平贡献 55 ++c1; 56 ans += dp_hor[r2][c2] - dp_hor[r2][c1 - 1] - dp_hor[r1 - 1][c2] + dp_hor[r1 - 1][c1 - 1]; 57 printf("%d\n",ans); 58 } 59 return 0; 60 }