前缀和与差分(一)

前缀和与差分

前缀和

前缀和是指某序列的前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

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值