前缀和
概念引入
众所周知,当你要查询区间和时,是不是要写这样一串代码:
int sum=0;
for(int i=a;i<=b;i++){
sum+=a[i];
}
而这样的方法时间复杂度为 O ( b − a ) O(b-a) O(b−a)。
经过多次查询,就会变得非常缓慢;
有没有一种快捷高效的方法呢?
当然是有的:那就是前缀和;
假设: b [ i ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + . . . . . . a [ i ] b[i]=a[1]+a[2]+a[3]+......a[i] b[i]=a[1]+a[2]+a[3]+......a[i]
则a[i]至a[j]的区间和为
b
[
j
]
−
b
[
i
]
b[j]-b[i]
b[j]−b[i]
sum[j]-sum[i]…好像打错了,不管他吧
这样只需要的一次sum数组的赋值预算,就可以进行查询了
代码如下:
for(int i=1;i<=k;i++){
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=q;i++){
int m,n;
cin>>m>>n;
cout<<sum[n]-sum[m]<<endl;
}
而用这样的方法,就可以在做类似查询最大区间和的题的时候把 O ( n 3 ) O(n^3) O(n3)的复杂度降低到 O ( n 2 ) O(n^2) O(n2),不知道快了多少。
习题
最大连续子序列和
题目描述
给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0,如果小于0,其结果为0。
输入格式
第一行输入一个整数n 第二行输入n个用空格分开的整数 a i a_i ai
输出格式
输出一个整数,表示这个序列的最大连续子序列的和
样例
样例输入1:
6
-2 11 -4 13 -5 -2
样例输出1:
20
样例输入2:
12
-6 2 4 -7 5 3 2 -1 6 -9 10 -2
样例输出2:
16
这种时候,前缀和就有他的用武之地了
#include<bits/stdc++.h>
using namespace std;
const int M=1e7+5;
int a[M];
long long sum[M],ms;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
sum[i]=a[i]+sum[i-1];//统计前缀和
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
ms=max(sum[j]-sum[i],ms);
}
}
cout<<ms;
}
差分
概念引入
差分跟前缀和在本质上是一样的
为了好理解,我们打个比方
减是加的逆运算
乘是除的逆运算
开方是乘方的逆运算
那么,差分就是前缀和的逆运算
众所周知,当你要区间加减时,是不是要写这样一串代码:
cin>>c;
for(int i=a;i<=b;i++){
a[i]+=c;
}
而这样的方法时间复杂度为
O
(
b
−
a
)
O(b-a)
O(b−a)。
这样还是太复杂了
而什么是差分呢
假设有两个数组
a
和
b
a和b
a和b
若
b
b
b的前缀和是
a
a
a
我们则称
b
b
b是
a
a
a的差分
a
[
i
]
=
b
[
1
]
+
b
[
2
]
+
b
[
3
]
+
.
.
.
.
.
.
b
[
i
]
a[i]=b[1]+b[2]+b[3]+......b[i]
a[i]=b[1]+b[2]+b[3]+......b[i]
而差分就可以帮我们进行简单的区间修改(感谢大佬的图)
如果我们把
d
4
d_4
d4加上
m
m
m
因为
a
a
a是
d
d
d的前缀和,所以
a
4
a_4
a4到
a
11
a_{11}
a11都将加上
m
m
m
可我们要的是
a
4
a_4
a4到
a
8
a_8
a8修改,怎么把后面的也改了?
这时候,我们就要在
d
9
d_9
d9减少对应的
m
m
m
因为
a
a
a是
d
d
d的前缀和,所以
a
9
a_9
a9到
a
11
a_{11}
a11都将减少
m
m
m
这样就能在
O
(
1
)
O(1)
O(1)的复杂度上进行区间修改
习题
差分
题目描述
输入一个长度为 n n n的整数序列。
接下来输入
m
m
m个操作,每个操作包含三个整数
l
,
r
,
c
l,r,c
l,r,c
表示将序列中
[
l
,
r
]
[l,r]
[l,r]之间的每个数加上
c
c
c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n n n和 m m m。
第二行包含n个整数,表示整数序列。
接下来 行,每行包含三个整数
l
,
r
,
c
l,r,c
l,r,c
表示一个操作。
输出格式
共一行,包含 n n n个整数,表示最终序列。
样例
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
数据范围与提示
1
≤
n
,
m
≤
100000
1≤n,m≤100000
1≤n,m≤100000
1
≤
l
≤
r
≤
n
1≤l≤r≤n
1≤l≤r≤n
−
1000
≤
c
≤
1000
-1000≤c≤1000
−1000≤c≤1000
−
1000
≤
a
i
≤
1000
-1000≤a_i≤1000
−1000≤ai≤1000
这个时候,如果暴力求解,肯定超时,就只能用差分了
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100005],b[100005];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i]-a[i-1];
}
for(int i=1;i<=m;i++){
int l,r,c;
cin>>l>>r>>c;
b[l]+=c;
b[r+1]-=c;
}
for(int i=1;i<=n;i++){
b[i]+=b[i-1];
cout<<b[i]<<" ";
}
return 0;
}
感谢大家的观看,下次我会给大家带来更有趣的二维前缀和与差分。