前缀和与差分
前缀和
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。
问题引入
输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l, r。对于每个询问,输出原序列中从第l个数到第r个数的和。
用暴力的方式 很容易实现,时间复杂度是O(n*m)。
但是如果我们进行预处理
const int N=1e7+5;
int sum[N],a[N];
for (int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];//即 sum[i]表示a[1]~a[i]的和
}
根据高中的数列知识很容易知道 a[i]=sum[i]-sum[i-1];
cout<<sum[i]-sum[i-1];//查询i开始到i结束的值
l~r 即可表示为:
(sum[r]-sum[r-1])+(sum[r-1]-sum[r-1-1])+...+sum[l]-sum[l-1];
化简成
sum[r]-sum[l-1];
输出原序列中从第l个数到第r个数的和。
cout<<sum[r]-sum[l-1];
即可解决,这样每次查询只用 输出结果即可,查询复杂度为O(1),这也是一维前缀和。
例题1
P1115 最大子段和
思路:
首先我们会考虑和之前一样的进行预处理 然后用两个for 遍历所有的查询,每次保留最大的。时间复杂度O(n*n)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int sum[N], a[N];
int n;
int ans = -0x3f;
int main ()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++) //考虑到是求最长字段和 我认为 一个也算字段
{
ans = max(ans, sum[j] - sum[i - 1]); //想到特判 i==0的时候,sum[1]-sum[0],全局变量sum[0]==0
}
}
cout << ans;
}
提交
TLE😠 害 看来暴力一定是不行的,想想还有什么地方可化简的。
其实仔细一看 我们可以在sum这里进行化简,也是简单的DP行为。然而这好像和今天的主题有所出入,但是我是一个专(sha)一(bi)的人,选了就不改了。我是蒟蒻你们不能骂我。所以以下方法 请选择性食用,上面能理解,前缀和的基本就知道一点了。
我们看样例
2 -4 3 -1 2 -4 3
从第一个开始
sum[1]=2
sum[2]=2±4||sum[2]=-4 很明显-2更大
sum[3]=-2+3 ||sum[3]=3 3大选3
诶,发现了嘛
dp[i]=max(a[i],dp[i-1]+a[i])
但是细心的同学会发现,其实dp[i]并不是表示题目的答案,
比如
5 233 233 -666 1 1
dp[1]=5;
dp[2]=233+5;
dp[3]=233+233+5;
dp[4]=dp[3]-666;
dp[5]=1;
dp[6]=dp[5]+1;
什么意思呢 按照程序意思,如果加上这个值更大,就加上。如果加上没有比他自己大,那就以自己为连续的起点。
所以dp[i]的意思是 以i为结尾的最大连续字段和
所以答案是dp[1]~dp[n]最大值
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int dp[N], a[N];
int n;
int ans = -9999999;//我不知道0x3f为什么会有一个样例出错 org
int main ()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
dp[i] = max(dp[i - 1] + a[i], a[i]);
ans = max(ans, dp[i]);
}
cout << ans;
}
AC!!!
分析一下 相比较原来的暴力访问,真的是在输入的时候就解决了问题。当然还可以在空间上进行优化,很明显只需要dp[i-1]利用滚动数组即可。
二维前缀和
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
我们用一个数组来表示 s[i] [j]
左上角(1,1)
到右下角( i,j )
从一个博主偷(读书的事怎么能叫偷呢)过来的图
很难不推出以下公式
s[i] [j] = s[i-1][j] + s[i][j-1 ] + a[i] [j] - s[i-1][ j-1]
所以x1,y1,x2,y2
s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]
练习题
题目练习: AcWing 797. 差分
输入一个长度为n的整数序列。
接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2