前缀和
一维前缀和
前缀和的定义:求前n项和
例题:给定一个数列 a n ( 1 ≤ n ≤ 100000 ) {a_n}(1≤n≤100000) an(1≤n≤100000),有 q ( 1 ≤ q ≤ 100000 ) q(1≤q≤100000) q(1≤q≤100000)次询问,每次询问树立的第m个元素到第n个元素的和。
如果是暴力解法:
有q次询问,每次都要扫一遍这个区间,最大时间复杂度是 O ( q × n ) O(q×n) O(q×n)
优化:(前缀和)
复杂度主要是在:q次查询,就是要扫q次区间。
前缀和的思想: 开一个更大的数组,将对区间的查询,转换为对区间端点的查询。
s u m [ i ] = s u m [ i − 1 ] + a [ i ] ; sum[i]=sum[i-1]+a[i]; sum[i]=sum[i−1]+a[i]; //前 i-1项的和加上第 i 项的数值
意思就是:开一个大数组,假设为
a
[
i
]
(
i
≥
n
)
a[i](i≥n)
a[i](i≥n),和为
s
u
m
[
i
]
=
s
u
m
[
i
−
1
]
+
a
[
i
]
sum[i]=sum[i-1]+a[i]
sum[i]=sum[i−1]+a[i],
a
[
m
]
a[m]
a[m]到
a
[
n
]
a[n]
a[n]的和等于
s
u
m
[
n
]
−
s
u
m
[
m
−
1
]
sum[n]-sum[m-1]
sum[n]−sum[m−1]
s
u
m
[
i
]
sum[i]
sum[i]是前缀和——算法竞赛中常用的小技巧。
前缀和的单次查询的时间复杂度是
O
(
1
)
O(1)
O(1) ,
q
q
q 次则是时间复杂度是
O
(
n
+
q
)
O(n+q)
O(n+q)
例题
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1
≤
l
≤
r
≤
n
1≤l≤r≤n
1≤l≤r≤n,
1
≤
n
,
m
≤
100000
1≤n,m≤100000
1≤n,m≤100000,
−
1000
≤
数
列
中
元
素
的
值
≤
1000
−1000≤数列中元素的值≤1000
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
#include<iostream>
using namespace std;
const int N = 100010;
int n,m;
int a[N],sum[N];
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++)
sum[i] = sum[i-1] + a[i];
while(m--)
{
int l,r;
scanf("%d%d", &l, &r);
printf("%d\n", sum[r] - sum[l-1]);//区间和的计算
}
return 0;
}
二维前缀和
求蓝色阴影部分面积:
S
x
2
y
2
−
S
x
2
y
1
−
1
−
S
x
1
y
2
−
1
+
S
x
1
−
1
y
1
−
1
S_{x_2y_2}-S_{x_2y_1-1}-S_{x_1y_2-1}+S_{x_1-1y_1-1}
Sx2y2−Sx2y1−1−Sx1y2−1+Sx1−1y1−1
前缀和公式: S i j = S i − 1 j + S i j − 1 − S j − 1 i − 1 + a i j S_{ij}=S_{i-1j}+S_{ij-1}-S_{j-1i-1}+a_{ij} Sij=Si−1j+Sij−1−Sj−1i−1+aij
例题:子矩阵的和
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。
输出格式
共q行,每行输出一个询问的结果。
数据范围
1
≤
n
,
m
≤
1000
1≤n,m≤1000
1≤n,m≤1000,
1
≤
q
≤
200000
1≤q≤200000
1≤q≤200000,
1
≤
x
1
≤
x
2
≤
n
1≤x1≤x2≤n
1≤x1≤x2≤n,
1
≤
y
1
≤
y
2
≤
m
1≤y1≤y2≤m
1≤y1≤y2≤m,
−
1000
≤
矩
阵
内
元
素
的
值
≤
1000
−1000≤矩阵内元素的值≤1000
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
#include<iostream>
#include<cstdio>
const int N = 1010;
int n,m,q;
int a[N][N],s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1;i <= n;i++)
for(int j = 1; j <= n;j++)
scanf("%d", &a[i][j]);
for(int i = 1 ;i <= m;i++)
for(int j = 1;j <= m;j++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];//计算前缀和
while(q--)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n",s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);//计算子矩阵
}
return 0;
}
差分
一维差分
a
1
,
a
2
,
a
3
,
.
.
.
,
a
n
a_1,a_2,a_3,...,a_n
a1,a2,a3,...,an 前缀
构造
b
1
,
b
2
,
b
3
,
.
.
.
,
b
n
b_1,b_2,b_3,...,b_n
b1,b2,b3,...,bn 差分
使得:
a
i
=
b
1
+
b
2
+
.
.
.
+
b
n
a_i=b_1+b_2+...+b_n
ai=b1+b2+...+bn
(对b[ ]求前缀和就是a[ ]);
其中:
b
1
=
a
1
b_1=a_1
b1=a1;
b
2
=
a
2
−
a
1
b_2=a_2-a_1
b2=a2−a1;
b
3
=
a
3
−
a
2
b_3=a_3-a_2
b3=a3−a2;
b
n
=
b
n
−
b
n
−
1
b_n=b_n-b_{n-1}
bn=bn−bn−1;
从
[
l
,
r
]
+
c
[ l,r ]+c
[l,r]+c 则
a
l
a_l
al+
c
+
a
l
+
1
c+a_{l+1}
c+al+1+
c
+
.
.
.
+
a
r
c+...+a_r
c+...+ar+
c
c
c
只需要在
[
l
,
r
]
[l,r]
[l,r] b加上即可
b
l
b_l
bl+
c
+
b
l
+
1
c+b_{l+1}
c+bl+1+
c
+
.
.
.
+
b
r
c+...+b_r
c+...+br+
c
c
c
还需要:
b
r
+
1
−
c
b_{r+1}-c
br+1−c
现在将时间复杂度从
O
(
n
)
O(n)
O(n)改为
O
(
1
)
O(1)
O(1)
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
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++)
b[i] = a[i] - a[i - 1];
while(m--)
{
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
b[l] += c;
b[r + 1] -= c;
}
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;
}
二维差分
例题:Acwing 798.差分矩阵
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1
≤
n
,
m
≤
1000
1≤n,m≤1000
1≤n,m≤1000,
1
≤
q
≤
100000
1≤q≤100000
1≤q≤100000,
1
≤
x
1
≤
x
2
≤
n
1≤x_1≤x_2≤n
1≤x1≤x2≤n,
1
≤
y
1
≤
y
2
≤
m
1≤y_1≤y_2≤m
1≤y1≤y2≤m,
−
1000
≤
c
≤
1000
−1000≤c≤1000
−1000≤c≤1000,
−
1000
≤
矩
阵
内
元
素
的
值
≤
1000
−1000≤矩阵内元素的值≤1000
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<map>
#include<algorithm>
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define ll long long
#define int ll
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define MOD 1e9 + 7
using namespace std;
int read()
{
int w = 1, s = 0;
char ch = getchar();
while (ch < '0' || ch>'9') { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { s = s * 10 + ch - '0';ch = getchar(); }
return s * w;
}
//最大公约数
int gcd(int x,int y) {
if(x<y) swap(x,y);//很多人会遗忘,大数在前小数在后
//递归终止条件千万不要漏了,辗转相除法
return x % y ? gcd(y, x % y) : y;
}
//计算x和y的最小公倍数
int lcm(int x,int y) {
return x * y / gcd(x, y);//使用公式
}
int ksm(int a, int b, int mod) { int s = 1; while(b) {if(b&1) s=s*a%mod;a=a*a%mod;b>>=1;}return s;}
//------------------------ 以上是我常用模板与刷题几乎无关 ------------------------//
const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];//b[i][j]记录的是相邻元素的差
//二维差分的核心
void insert(int x1, int y1, int x2, int y2, int c)
{
//只需要处理4个点 (将O(n)的时间复杂度变成O(1))
b[x1][y1] += c;//将(x1,y1)右下角的所有点+c
b[x2 + 1][y1] -= c;//将(x2+1,y1)右下角的所有点-c
b[x1][y2 + 1] -= c;//将(x1,y2+1)右下角的所有点-c
b[x2 + 1][y2 + 1] += c;//将(x2+1,y2+1)右下角的所有点+c
}
signed main()
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
scanf("%lld", &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]);
while (q--) {
int x1, y1, x2, y2, c;
scanf("%lld%lld%lld%lld%lld", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
}
//求前缀和
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++)
printf("%lld ", b[i][j]);
printf("\n");
}
return 0;
}
前缀和与差分的应用 (洛谷P3397 地毯)
题目描述
在
n
×
n
n\times n
n×n 的格子上有
m
m
m 个地毯。
给出这些地毯的信息,问每个点被多少个地毯覆盖。
输入格式
第一行,两个正整数
n
n
n,
m
m
m。
意义如题所述。
接下来
m
m
m 行,每行两个坐标
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)和
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2),代表一块地毯,左上角是
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1),右下角是
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2)。
输出格式
输出
n
n
n 行,每行
n
n
n 个正整数。
第 i i i 行第 j j j 列的正整数表示 ( i , j ) (i,j) (i,j) 这个格子被多少个地毯覆盖。
输入输出样例
输入
5 3
2 2 3 3
3 3 5 5
1 2 1 4
输出
0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1
说明/提示
样例解释
覆盖第一个地毯后:
0 0 0 0 0
0 1 1 0 0
0 1 1 0 0
0 0 0 0 0
0 0 0 0 0
覆盖第一、二个地毯后:
0 0 0 0 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1
覆盖所有地毯后:
0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1
数据范围
对于 20% 的数据,有 n ≤ 50 n\le 50 n≤50, m ≤ 100 m\le 100 m≤100。
对于 100% 的数据,有 n n n, m ≤ 1000 m\le 1000 m≤1000。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<map>
#include<algorithm>
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define ll long long
#define int ll
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define MOD 1e9 + 7
using namespace std;
int read()
{
int w = 1, s = 0;
char ch = getchar();
while (ch < '0' || ch>'9') { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { s = s * 10 + ch - '0';ch = getchar(); }
return s * w;
}
//最大公约数
int gcd(int x,int y) {
if(x<y) swap(x,y);//很多人会遗忘,大数在前小数在后
//递归终止条件千万不要漏了,辗转相除法
return x % y ? gcd(y, x % y) : y;
}
//计算x和y的最小公倍数
int lcm(int x,int y) {
return x * y / gcd(x, y);//使用公式
}
int ksm(int a, int b, int mod) { int s = 1; while(b) {if(b&1) s=s*a%mod;a=a*a%mod;b>>=1;}return s;}
//------------------------ 以上是我常用模板与刷题几乎无关 ------------------------//
const int N = 1010;
int a[N][N];
signed main()
{
int n = read(), m = read();
while (m--) {
int x1 = read(), y1 = read(), x2 = read(), y2 = read();
a[x1][y1]++;
a[x2 + 1][y1]--;
a[x1][y2 + 1]--;
a[x2 + 1][y2 + 1]++;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (j == n) printf("%lld\n", a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1]);
else printf("%lld ", a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1]);
}
}
return 0;
}