1.一维前缀和
目的:
设有一n项数组a,求
调用时只用O(1),预处理O(n)
做法:
1.定义数组pre,
这样就可以计算了:如:l=4,r=6
PS:是pre[r]-pre[l+1]
是pre[r]-pre[l+1]
是pre[r]-pre[l+1]
重要的事情说三遍
Problem
求
要O(i)-->就算是O(n)
那球 pre 就要……
那还是不满足要求
那……
pre[i]=pre[i-1]+a[i]
后缀和
用法:见后文
rsum存值的伪代码:
for(i=n~1) rsum[i]=rsum[i+1]+a[i]
Description
需要实现一个可以快速求数列区间和的程序,你也来试试吧!
Format
Input
第一行输入两个正整数 n(1<n<
)和 m(1<m <
)。
第二行连续输入 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<,不能用 cin & cout
以为前缀和的就这么简单?NO,魅力无穷!
这只是基本运用!
再来看一道题目
轩轩和凯凯正在玩一款叫《龙虎斗》的游戏,游戏的棋盘是一条线段,线段上有 n 个兵营(自左至右编号
,相邻编号的兵营之间相隔 1 厘米,即棋盘为长度为 n-1 厘米的线段。i 号兵营里有 位工兵。 下图为 n=6 的示例:
轩轩在左侧,代表“龙”;凯凯在右侧,代表“虎”。 他们以 m 号兵营作为分界, 靠左的工兵属于龙势力,靠右的工兵属于虎势力,而第 m 号兵营中的工兵很纠结,他们不属于任何一方。
一个兵营的气势为:该兵营中的工兵数= 该兵营到
m 号兵营的距离;参与游戏 一方的势力定义为:属于这一方所有兵营的气势之和。下图为n=6,m=4 的示例,其中红色为龙方,黄色为虎方:
设 m=4 :
龙:
虎:
现在告诉你每一个兵营的工兵数量,请你设定分界兵营,使龙虎的总战斗力差值最小。请求出这个差值。
【输入格式】
第一行一个整数 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% 数据:1n
,
。
一看,一个字:“懵”,没思路啊!
注释:
此处pre与ls的意思一样思路:
龙:
懂了吧!
虎和前缀和一样的意思,此处不展示
然后还是
啊
对了,定一个数组是前缀和的前缀和就ok了,(还有后缀和的后缀和)
其实这题可以只用一次前缀和&后缀和,是定义l=0,r=
然后每次 l+
,r-
,此处就不展示了
#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[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.差分
目标:
每个值都加 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(
),m(
)。
第二行连续输入 nn 个整数。
接下来连续输入 m 行,每行 3 个整数 l,r,x(0<l≤r≤n,
),表示对 [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;
}