目录
1.定义
2.作用
3.例题:【模板】一维前缀和
分析
方法1:暴力解法
方法2:前缀和(简单的动态规划)
第一步:预处理
4.练习:P1115 最大子段和
分析
方法1:段长从1枚举到n
方法2:改进方法1
代码
提交结果
1.定义
快速求出数组中某一段的区间和,时间复杂度为(速度极快)
2.作用
可在暴力枚举的过程中快速给出查询的结果,用空间替换时间,从而优化时间复杂度
3.例题:【模板】一维前缀和
https://ac.nowcoder.com/acm/problem/226282
分析
注意到数组从下标为1位置开始计数,原因见之后的算法推导
方法1:暴力解法
直接的想法:先用arr[]存读到的数,读到l和r时就按部就班地计算arr[l]+arr[l+1]+...+arr[r]
注意使用long long存储求和的结果,用int可能会溢出
#include <iostream>
#define endl "\n"
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,q,l,r;
const long long N=1e5+10;
long long arr[N];
cin>>n>>q;
for (int i=1;i<=n;i++)
cin>>arr[i];
while(q--)
{
long long sum=0;
cin>>l>>r;
for (int j=l;j<=r;j++)
sum+=arr[j];
cout<<sum<<endl;
}
}
运行结果:运行超时
反思:时间复杂度按最差情况算,如果每次都要计算arr[1]~arr[n]的和,时间复杂度为(q为询问次数
方法2:前缀和(简单的动态规划)
第一步:预处理
预处理一个数组dp[](叫dp的原因是动态规划的英文为dynamic programming,取首字母),其中元素dp[i]表示arr[1]+...+arr[i]的求和结果(即闭区间[1,i]中所有元素的和)
使用递推公式dp[i]=dp[i-1]+dp[i]来快速预处理,时间复杂度为,而若每次计算f[i]都有反复计算arr[1]+...+arr[i],时间复杂度为
★则arr[l]+...+arr[r]等于dp[r]-dp[l-1]
设计代码时,在读arr[i]时就可以预处理前缀和数组
即如果用b表示数组的前n项和,其中
,那么有
(n>=2)(这个就是状态转移方程,等价表示为dp[i]=dp[i-1]+arr[i]),可以使用数组dp[N]来存储所有的
(1<=i<=n)
#include <iostream>
#define endl "\n"
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,q,l,r;
const long long N=1e5+10;
long long arr[N];
long long dp[N];
cin>>n>>q;
cin>>arr[1];//注意从下标为1开始计数
dp[1]=arr[1];
for (int i=2;i<=n;i++)
{
//一边读arr[i],一边写入dp数组
cin>>arr[i];
dp[i]=dp[i-1]+arr[i];
}
while(q--)
{
cin>>l>>r;
cout<<dp[r]-dp[l-1]<<endl;
}
}
(注意从下标从1开始计数,这样好处理,从0开始容易越界访问,例如[0,2]区间的元素的和:dp[2]-dp[-1],-1越界了,设置dp[0]=0,不干扰计算结果)
若i从1开始循环,前缀和计算代码也可以这样写:
dp[0]=0;
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>arr[i];
//i==1时,dp[i-1]==dp[0]
dp[i]=dp[i-1]+arr[i];
}
运行结果:
4.练习:P1115 最大子段和
https://www.luogu.com.cn/problem/P1115
分析
读题可知:"使得这段和最大"应该使用前缀和算法
方法1:段长从1枚举到n
#include <iostream>
#include <climits>
#define endl "\n"
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
const long long N=2e5+10;
long long arr[N];
long long dp[N];
long long max=LLONG_MIN;
cin>>n;
cin>>arr[1];
dp[1]=arr[1];
for (int i=2;i<=n;i++)
{
cin>>arr[i];
dp[i]=dp[i-1]+arr[i];
}
for (int len=1;len<=n;len++)//段长从1枚举到n
{
for (int index=1;index+len-1<=n;index++)
{
long long sub=dp[index+len-1]-dp[index-1];
if (sub>max)
max=sub;
}
}
cout<<max;
}
注:long long的最小值的宏为LLONG_MIN,定义在头文件<climits>中
运行结果:超时,算法需要改进,两层for循环时间复杂度过高
方法2:改进方法1
算法:
设数组arr存储读入的n个元素,求以元素arr[i]为结尾的最大子段的和,可以画图演示计算方法
则以元素arr[i]为结尾的子段的和为dp[i]-dp[x],如果要求子段和最大,那么有:
(注:设i为常数)
定义为ret,
为prevmin,显然,求最大使用max函数,求最小使用min函数
暂时写为:
ret=max(?,ret);
prevmin=min(?,prevmin);
填补?处:
由于i从1开始因此一开始ret==max(dp[1]-prevmin,ret)==dp[1],那么prevmin的初始值为0(max(dp[1]-prevmin,ret)为max(dp[1],ret)),则可以写出:
ret=max(dp[i]-prevmin,ret);
prevmin=min(dp[i],prevmin);
代码
#include <iostream>
#include <algorithm>
#define endl "\n"
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,tmp;
long long dp[200005];
long long prevmin=0,ret=-1e5-10;//ret初始化为负无穷大
cin>>n;
dp[0]=0;
for (int i=1;i<=n;i++)
{
cin>>tmp;
dp[i]=dp[i-1]+tmp;//arr数组可以省略,下面没有用到
}
for (int i=1;i<=n;i++)
{
ret=max(dp[i]-prevmin,ret);
prevmin=min(prevmin,dp[i]);
}
cout<<ret;
}