前缀和和差分(1维)

1.一维前缀和

目的:

设有一n项数组a,求\sum_{i=l}^{r} a_{i}

调用时只用O(1),预处理O(n)

做法:

1.定义数组pre,pre_j=\sum_{i=1}^j a_i

这样就可以计算了:如:l=4,r=6

 PS:是pre[r]-pre[l+1]

是pre[r]-pre[l+1]

是pre[r]-pre[l+1]

重要的事情说三遍

 Problem

        求pre_{i}要O(i)-->就算是O(n)

        那球 pre 就要……O(n^{2})

        那还是不满足要求

那……

pre[i]=pre[i-1]+a[i]

后缀和

rsum_j=\sum_{i=j}^n a_i

用法:见后文

rsum存值的伪代码:

for(i=n~1) rsum[i]=rsum[i+1]+a[i]

Description

需要实现一个可以快速求数列区间和的程序,你也来试试吧!

Format

Input

第一行输入两个正整数 n(1<n<10^{6})和 m(1<m <10^{6})。

第二行连续输入 nn个 int 型整数,用 1 个空格隔开。

接着连续 m 行,每行两个正整数 x,y,用 1 个空格隔开,表示一次询问,[x,y] 区间内的元素和是多少。

Output

输出共 m 行,每行 1 个整数,表示该数列在对应区间 [x,y] 范围内的元素之和(保证在long long 的范围内)。

Samples

输入数据 1

5 3
9 1 2 0 6
2 4
1 3
2 5

输出数据 1

3
12
9

伪代码: 

//只写main
输入
for(i=1~n) pre[i]=pre[i-1]+a[i];
for(i=1~m){
    输入
    输出pre[r]-pre[l-1]
}

#include <cstdio>
const int N=1e6+7;
long long a[N];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%lld",a+i);
		a[i]+=a[i-1];
	}
	for (int i=1,l,r;i<=m;++i){
		scanf("%d%d",&l,&r);
		printf("%lld\n",a[r]-a[l-1]);
	}
	return 0;
}

 此代码a直接pre用了

注意点(我原来没过的原因):

        1).long long的问题

        2).n,m<10^{6},不能用 cin & cout

以为前缀和的就这么简单?NO,魅力无穷!

这只是基本运用!

再来看一道题目

轩轩和凯凯正在玩一款叫《龙虎斗》的游戏,游戏的棋盘是一条线段,线段上有 n 个兵营(自左至右编号 1\le i \le n,相邻编号的兵营之间相隔 1 厘米,即棋盘为长度为 n-1 厘米的线段。i 号兵营里有 ​ 位工兵。 下图为 n=6 的示例:

轩轩在左侧,代表“龙”;凯凯在右侧,代表“虎”。 他们以 m 号兵营作为分界, 靠左的工兵属于龙势力,靠右的工兵属于虎势力,而第 m 号兵营中的工兵很纠结,他们不属于任何一方。

一个兵营的气势为:该兵营中的工兵数= 该兵营到 \times m 号兵营的距离;参与游戏 一方的势力定义为:属于这一方所有兵营的气势之和。下图为n=6,m=4 的示例,其中红色为龙方,黄色为虎方:

设 m=4 :

龙: 1\times (4-1)+2\times (4-2)+3\times(4-3)=10

虎:5\times(5-4)+6\times(6-4)=17

现在告诉你每一个兵营的工兵数量,请你设定分界兵营,使龙虎的总战斗力差值最小。请求出这个差值。

【输入格式】

第一行一个整数 n。

第二行 n 个整数 

【输出格式】

一个整数表示答案。

【输入输出样例#1】

输入#1

5
1 2 3 4 5

输出#1

5

【输入输出样例#2】

输入#2

10
1 7 5 4 2 3 5 7 8 9

输出#2

21

【数据范围】

对于 100% 数据:1 \le n \le10^{5},0\le a_i \le 10^9

一看,一个字:“懵”,没思路啊!

注释:
此处pre与ls的意思一样

思路:

龙:\sum _{i=1}^m pre_i

 懂了吧!

虎和前缀和一样的意思,此处不展示

然后还是O(n^2)

对了,定一个数组是前缀和的前缀和就ok了,(还有后缀和的后缀和)

其实这题可以只用一次前缀和&后缀和,是定义l=0,r=\sum_{i=1}^n rs_i

然后每次 l+ls_i,r-rs_i,此处就不展示了

#include <bits/stdc++.h>
using namespace std;
#define N 101000
#define int long long
int n,val[N],ls[N],rs[N],lss[N],rss[N];
signed main() {
  	cin>>n;
  	for(int i=1;i<=n;++i) cin>>val[i];
  	
  	for(int i=1;i<=n;++i) ls[i]=ls[i-1]+val[i];
  	for(int i=n;i>=1;--i) rs[i]=rs[i+1]+val[i];
  	
  	for(int i=1;i<=n;++i) lss[i]=lss[i-1]+ls[i];
  	for(int i=n;i>=1;--i) rss[i]=rss[i+1]+rs[i];
  	
  	int ans=1e18;
	for(int i=1;i<=n;++i)ans=min(ans,abs(lss[i-1]-rss[i+1]));
	cout<<ans;
    return 0;
}

现在前缀和也知道了,后缀和也知道了,把前缀积与后缀积说一下

pre_i=a_1 \times a_2 \times a_3 \times......a_i

储存方法

前缀积
pre[0]=1;//重中之重,否则全0
for(i=1~n) pre[i]=pre[i-1]*a[i];
后缀积
rs[n+1]=1//重中之重,否则全0
for(i=n~1) rs[i]=rs[i+1]*a[i];

 2.差分

目标:

a_l,a_{l+1},......,a_r,每个值都加 x

调用O(1),最后加一个O(n)

很玄幻的东西,做法

1.每次 s[l]+=x,s[r+1]-=x

2.做前缀和

记得要加上原来的值

但别原来在a上改,因为要做前缀和

这个不难,就放一道例题 

Background

学会了前缀和以后,小 J 想通过前缀和算法解锁一些新技能。

现在,他想对一个整数序列进行一系列的区间加减,求出调整后的序列。

Description

例如:对于 5 个元素的序列 9 1 2 5 3,将 [2, 4] 区间的所有数都加上 2,再将 [1, 4] 区间的所有数加上 5,再将 [4, 5] 区间的所有数减去 2,得到的序列是14 8 9 10 1。

这样的代码该怎么写呢?

Format

Input

第一行输入两个正整数 n(1\le n\le10^6),m(1\le m\le10^6)。

第二行连续输入 nn 个整数。

接下来连续输入 m 行,每行 3 个整数 l,r,x(0<l≤r≤n,-10^6\le x \le 10^6),表示对 [l,r] 区间的所有数都加上一个 x。

Output

输出 1 行 n个整数,是经过调整后的序列。

Samples

输入数据 

5 3
9 1 2 5 3
2 4 2
1 4 5
4 5 -2

输出数据 

14 8 9 10 1
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+7;
typedef long long ll;
ll a[N],s[N],pre[N];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		scanf("%d",a+i);
	for (int i=1,a,b,c;i<=m;++i){
		scanf("%d%d%d",&a,&b,&c);
		s[a]=c,s[b+1]=-c;//差分
	}
	for(int i=1;i<=n;++i){
        pre[i]=pre[i-1]+s[i];
        a[i]+=pre[i];//前缀和
    }
	for(int i=1;i<=n;++i) printf("%d ",a[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值