课堂学习
前缀和数组
前1个收购点:3箱
前2个收购点:3+2=5箱
前3个收购点:3+2+5=10箱
以此类推…
数组a存储10个收购点的箱数。
收购点编号从1~10,数组下标也从1开始使用。
下标0位置直接赋值0
#include<bits/stdc++.h>
using namespace std;
int main(){//创建并初始化数组a:从下标1开始用,下标0赋值0int a[11]={0,3,2,5,6,6,1,4,1,7,3};//创建并初始化数组sint s[11]={};//1.初始条件:s[1]=a[1]s[1]=a[1];//2.根据递推关系推导前缀和:s[i]=s[i-1]+a[i] (i>=2)for(int i=2;i<=10;i++){s[i]=s[i-1]+a[i];}//3.循环输出前缀和for(int i=1;i<=10;i++){cout<<s[i]<<" ";}return 0;
}
课堂训练
2915 计算区间和
描述
输入 n 个整数,再输入m个区间,每个区间的起点下标L,终点下标R。
对于每个区间,输出n个整数中从下标L到下标R的区间和。
输入描述
第一行包括两个整数n和m。(1≤n,m≤500000)
第二行包括n个整数。(1≤整数≤100)
接下来有m行,每行包含两个整数L和R,表示区间范围。(0<L≤R≤n)
输出描述
输出有m行,每行一个整数,表示一个区间和。
样例输入 1
10 3 2 1 3 6 4 20 15 10 4 11 3 7 1 9 5 8
样例输出 1
48 65 49
简单分析
//2915 计算区间和
#include<bits/stdc++.h>//万能头文件
using namespace std;
const int N=500010;
int a[N],s[N];//a是原数组,s是前缀和数组
int main(){//输入数据int n,m;cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}s[1]=a[1];//求出任意位置的前缀和for(int i=2;i<=n;i++){s[i]=s[i-1]+a[i];}int L,R;for(int i=1;i<=m;i++){cin>>L>>R;cout<<s[R]-s[L-1]<<endl;}return 0;
}
练一练:
总结:前缀和
③前缀和优势:可以1步减法算出区间和。
下标[L~R]的区间和=s[R]-s[L-1]
④前缀和使用场景:多次计算区间和。
注意:区间起点L一般从1开始。
如果L=0,就会导致s[L-1]为s[-1]的情况。
2916 K个元素和
描述
输入n个整数,求出所有连续且长度为K的元素总和。
输入描述
第一行包括两个整数n和K。(1≤K≤n≤500000)
第二行包括n个整数。(1≤整数≤100)
输出描述
输出一行,包含若干数字,每个数字表示1个元素总和。
样例输入 1
10 3 2 1 3 6 4 5 8 7 0 9
样例输出 1
6 10 13 15 17 20 15 16
提示
根据样例得知,在10个数字中求连续且长度为3的元素总和,依次得到:
2+1+3=6;1+3+6=10;3+6+4=13;6+4+5=15;
4+5+8=17; 5+8+7=20; 8+7+0=15; 7+0+9=16。
#include<bits/stdc++.h>
using namespace std;
int a[500010]={};//原数组
int s[500010]={};//前缀和数组
int main(){int n,K;cin>>n>>K;//1.输入n个元素; 注意从下标1开始使用for(int i=1;i<=n;i++){cin>>a[i];}//2.循环推导前缀和s[1]=a[1];for(int i=2;i<=n;i++){s[i]=s[i-1]+a[i];}//3.遍历所有长度K的区间;计算输出区间和for(int i=K;i<=n;i++){cout<<s[i]-s[i-K]<<" ";}return 0;
}
4434 m倍的区间
描述
输入 n 个整数,在所有连续且长度为 K 的区间中,统计有多少区间和是 m 的倍数。
输入描述
第一行包括三个整数 n,K 和 m。
第二行包括 n 个整数。
输出描述
输出一个整数,表示有多少个区间和是 m 的倍数。
样例输入 1
5 3 2 2 1 3 6 4
样例输出 1
2
提示
1≤m≤K≤n≤500000,1≤整数≤100
长度为3的区间有:2 1 3,1 3 6,3 6 4。
区间2 1 3和1 3 6的和是2的倍数。
#include<bits/stdc++.h>
using namespace std;
int a[500010]={};//原数组
int s[500010]={};//前缀和数组
int main(){int n,k,m;cin>>n>>k>>m;for(int i=1;i<=n;i++){cin>>a[i];}//推导前缀和数组s[1]=a[1];for(int i=2;i<=n;i++){s[i]=s[i-1]+a[i];}int ans=0;//遍历所有长度K的区间for(int i=k;i<=n;i++){int t=s[i]-s[i-k]; //计算区间和if(t%m==0){ //判断区间和是m倍数ans++;}}cout<<ans;return 0;
}
4433 最大区间和
描述
输入 n 个整数,在所有连续且长度为K的区间中,找到最大的区间和。
输入描述
第一行包括两个整数 n 和 K。
第二行包括 n 个整数。
输出描述
输出两行。第一行一个整数,表示最大区间和。
第二行两个整数,表示最大区间和的起点下标和终点下标。
样例输入 1
10 3 2 1 3 6 4 5 8 7 5 3
样例输出 1
20 6 8
提示
1≤K≤n≤500000,1≤整数≤100
样例中最大区间下标范围:6~8,区间和为20。
注意:如果多个区间和同为最大,取第1个区间。
#include<bits/stdc++.h>
using namespace std;
int a[500010]={};//原数组
int s[500010]={};//前缀和数组
int main(){int n,K;cin>>n>>K;//1.输入n个元素; 注意从下标1开始使用for(int i=1;i<=n;i++){cin>>a[i];}//2.循环推导前缀和s[1]=a[1];for(int i=2;i<=n;i++){s[i]=s[i-1]+a[i];}//3.遍历所有长度K的区间//先计算区间和,再取最大的区间和int res=0;//最大区间和int b,e;//起点下标b,终点下标efor(int i=K;i<=n;i++){//终点下标:i//起点的前1个下标:i-Kint t=s[i]-s[i-K];if(t>res){res=t;b=i-K+1;e=i;}}//4.输出结果cout<<res<<endl;cout<<b<<" "<<e<<endl;return 0;
}
差分
练一练
差分性质
差分理论
4436 混合操作
描述
输入 n 个整数,计算区间和。
输入描述
第一行包括一个整数 n。
第二行包括 n 个整数。
第三行包括一个整数 m,表示需进行 m 次操作。
操作包括两种:1 表示计算区间和;2 表示修改 n 个整数的其中 1 个。(操作 2 只有 1 次)
接着有 m 行,每行表示 1 次操作:
如果第一个数字为 1,后面跟着区间的起点 L,终点 R;
如果第一个整数为 2,后面跟着被修改整数所在位置 k,修改为整数 num。
输出描述
输出 m−1 行,每行一个整数,表示一个区间和。
样例输入 1
10 2 1 3 6 4 20 15 10 4 11 5 1 3 7 1 4 9 2 8 20 1 7 10 1 5 8
样例输出 1
48 59 50 59
提示
1≤n≤100000,1≤整数≤100,1≤m≤100000
对 10 个整数做 5 次操作:
第1次:计算[3~7]区间和,结果48;
第2次:计算[4~9]区间和,结果59;
第3次:修改第8个数字10,修改为20;
第4次:计算[7~10]区间和,结果50;
第5次:计算[5~8]区间和,结果59。
#include<bits/stdc++.h>
using namespace std;
int a[100010]={}; //原数组
int s[100010]={}; //前缀和数组
int main(){int n,m;cin>>n;//1、输入n个整数for(int i=1;i<=n;i++) cin>>a[i];//2、推导前缀和s[1]=a[1];for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i];//3、遍历m次操作,判断并处理cin>>m;int c;//操作标记int L,R;int k,num;for(int i=1;i<=m;i++){ //m次操作cin>>c;if(c==1){ //计算区间和cin>>L>>R;cout<<s[R]-s[L-1]<<endl;}else{ //修改一个整数cin>>k>>num;a[k]=num; //元素a[k]赋值num//重新计算前缀和s[1]=a[1];for(int j=2;j<=n;j++) s[j]=s[j-1]+a[j];}}return 0;
}
课后作业
2919 最要强的飞行员
描述
在一次电子模拟作战中,假设敌方设置了一条防线,防线上依次有n个据点。每个据点都有一个牢固值,数值越大表示越牢固。
司令部计划对n个据点进行m轮攻击,每轮攻击一段连续范围的据点,每段范围上的据点都存在一个总牢固值。
有一位最要强的飞行员,申请在总牢固值最大的一轮出战。
请你编写程序找到最大的总牢固值。
输入描述
第一行包括两个整数n和m。(1≤n,m≤500000)
第二行包括n个整数,依次表示n个据点的牢固值。(1≤整数≤100)
接下来m行,每行两个正整数L和R,表示一轮范围。(1≤L≤R≤n)
输出描述
输出一个整数,表示最大的总牢固值。
样例输入 1
7 2 2 10 5 3 6 4 9 3 5 6 7
样例输出 1
14
提示
输入样例中m=2,表示有2轮攻击。
第1轮攻击从3~5,总牢固值5+3+6=14。
第2轮攻击从6~7,总牢固值4+9=13。
两轮攻击总牢固值最大14。
#include<bits/stdc++.h>
using namespace std;
int a[500001],s[500001];
int main(){int n,m;int L,R,res=0;//res存储最大总牢固值cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];//输入据点牢固值s[i]=s[i-1]+a[i];//计算该据点前缀和}for(int i=1;i<=m;i++){cin>>L>>R;//输入一轮打击范围if((s[R]-s[L-1])>res){ //判断当前据点总牢固值是否大于resres=s[R]-s[L-1];}}cout<<res;//输出最大总牢固值resreturn 0;
}
2939 纸牌PK
描述
小童和小程每次遇到谁优先的问题,都会采用抽一张纸牌比大小的方式决定,总采用这种方式,难免感到无趣。
小童今天突发奇想,修改了抽纸牌的方式。修改后的方式是这样的:两人轮流在n张纸牌中抽取m轮,每轮抽取连续一定范围的纸牌,计算m轮抽取中所有牌面上的数字总和,最终谁的数字总和大,谁获得优先权。
输入描述
第一行包括两个整数n和m。(1≤m,n≤500000)(1≤m≤100)
第二行包括n个整数,依次表示n张纸牌上的数字。(1≤整数≤100)
接下来m行,每行两个正整数L和R,表示小童每轮抽牌的范围。
接下来m行,每行两个正整数L和R,表示小程每轮抽牌的范围。(1≤L≤R≤n)
输出描述
输出一个字符,小童数字总和大,输出T;小程数字总和大,输出C;总和相等输出D。
样例输入 1
7 3 2 10 5 3 6 4 9 3 5 6 7 2 7 2 6 1 2 1 6
样例输出 1
C
提示
输入样例中m=3,表示两人各自抽3轮范围纸牌。
小童第1轮:抽取第3~5张纸牌,该轮数字和5+3+6=14。
小童第2轮:抽取第6~7张纸牌,该轮数字和4+9=13。
小童第3轮:抽取第2~7张纸牌,该轮数字和10+5+3+6+4+9=37。
小童数字总和14+13+37=64。
小程第1轮:抽取第2~6张纸牌,该轮数字和10+5+3+6+4=28。
小程第2轮:抽取第1~2张纸牌,该轮数字和2+10=12。
小程第3轮:抽取第1~6张纸牌,该轮数字和2+10+5+3+6+4=30。
小程数字总和28+12+30=70。
最终小程数字总和大于小童,输出字符C。
#include<bits/stdc++.h>
using namespace std;
int a[500001],s[500001];
int main(){int n,m;long long sum_t=0,sum_c=0;int L,R;cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];s[i]=s[i-1]+a[i]; //计算前缀和}for(int i=1;i<=m;i++){cin>>L>>R;sum_t=sum_t+(s[R]-s[L-1]); //用前缀和计算范围纸牌总和;并累加}for(int i=1;i<=m;i++){cin>>L>>R;sum_c=sum_c+(s[R]-s[L-1]); //用前缀和计算范围纸牌总和;并累加}if(sum_t>sum_c){//小童m轮纸牌总数大cout<<'T';}else if(sum_t<sum_c){//小程m轮纸牌总数大cout<<'C';}else{//平局cout<<'D';}return 0;
}
2918 物资准备
描述
某国测试一种新型火炮,对一条路线进行打击。
这条路线上有n个据点,每个据点都有一个牢固值,1发炮弹消耗1点牢固值,假设牢固值为10的据点,需要10发炮弹摧毁。
现在共有m门火炮参与测试,每门火炮摧毁一段连续范围的据点。
由于指挥混乱,m门火炮发射前没有沟通,可能存在炮弹浪费的情况。
问合计需要准备多少发炮弹。
输入描述
输入描述
第一行包括两个整数n和m。(1≤n,m≤100000)
第二行包括n个整数,依次表示n个据点的牢固值。(1≤整数≤100)
接下来m行,每行两个正整数L和R,表示一门火炮的摧毁范围。(1≤L≤R≤n)
输出描述
输出一个整数,表示炮弹总数。
样例输入 1
7 2 2 10 5 3 6 4 9 3 5 6 7
样例输出 1
27
样例输入 2
5 3 4 2 10 3 7 1 2 4 5 1 3
样例输出 2
32
提示
路线上有 7 个据点,2 门火炮参与。
7个据点的牢固值依次为:2,10,5,3,6,4,9。
第1门火炮摧毁第3,4,5据点,需发射炮弹数量:5+3+6=14。
第2门火炮摧毁第6,7据点,需发射炮弹数量:4+9=13。
合计需要准备 27 发炮弹。
路线上有 5 个据点,3 门火炮参与。
5个据点的牢固值依次为:4,2,10,3,7。
第1门火炮摧毁第1,2据点,需发射炮弹数量:4+2=6。
第2门火炮摧毁第4,5据点,需发射炮弹数量:3+7=10。
第3门火炮摧毁第1,2,3据点,需发射炮弹数量:4+2+10=16。(无需考虑炮弹浪费的情况)
合计需要准备 32 发炮弹。
#include<bits/stdc++.h>
using namespace std;
int a[100010]; //原数据
int s[100010]; //前缀和
int main(){int n,m;cin>>n>>m;//输入原数据for(int i=1;i<=n;i++) cin>>a[i];//计算前缀和ss[1]=a[1];for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i];//总数 注意总数会超过int范围,用long long类型long long sum=0;for(int i=1;i<=m;i++){int L,R;cin>>L>>R;sum+=(s[R]-s[L-1]);}cout<<sum;return 0;
}
3212 有趣的求和
描述
给出n个数排成一排,你可以任意选出连续的L个数字求和。例如:
n=5 L = 4
-20 30 80 50 40
连续取L个数的方法有两种。
1、取前4个数-20 30 80 50 和为140。
2、取后4个数30 80 50 40 和为200。
请你找出最大和是多少,上例结果应该为200。
输入描述
第1行为两正整数n和L表示数列数字个数和取的长度;
第2行n个整数空格分隔,表示数列中的每个元素,数字在-100到100之间的整数。
输出描述
输出一个整数,最大的数字和。
样例输入 1
5 4 -20 30 80 50 40
样例输出 1
200
提示
30%的数据1≤L≤n≤100。
50%的数据1≤L≤n≤10000。
100%的数据 1≤L≤n≤1000000。
#include<bits/stdc++.h>
using namespace std;
int a[1000001],s[1000001];
int main(){int n,l;cin>>n>>l;for(int i=1;i<=n;i++) cin>>a[i];//计算前缀和s[1]=a[1];for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i];//创建变量存储最大数字和//初始化成比可能出现的最小值还小int max1=-1000000000;for(int i=l;i<=n;i++){if(max1<s[i]-s[i-l]) max1=s[i]-s[i-l];}cout<<max1;return 0;
}