前缀和与差分

前缀和与差分:
前缀和:即指某个序列的前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 }

 

posted @ 2021-07-27 11:39  jue1e0  阅读(196)  评论(0编辑  收藏  举报