参考课程是我高中信息竞赛邱老师的课程。
完整的板子可以去资源获取,我设置0积分,应该是免费的,如果C收你费或者下不了B站私我。
前缀和(用于计算区间和 出现Σ都先考虑前缀和)
得到前缀和关系,计算区间和
方法两步:
①创建原数组 和 presum数组(从1开始)
②sumi=sumi-1+ai; 得到前缀和关系
②输出sumr-suml-1 输出区间和
一维前缀和

模板 P8218
https://www.luogu.com.cn/problem/P8218

cpp
#include<bits/stdc++.h>
using namespace std;
int a[1000005],sum[1000005];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=a[i]+sum[i-1];
}
int m;
cin>>m;
for(int i=0;i<m;i++){
int r,l;
cin>>l>>r;
cout<<sum[r]-sum[l-1]<<endl;
}
return 0;
}
练习
P1614
https://www.luogu.com.cn/problem/P1614

cpp
#include<bits/stdc++.h>
#include<limits.h>
using namespace std;
int a[3005],presum[3005];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
presum[i]=presum[i-1]+a[i];
}
int minn=INT_MAX;
for(int i=0;i<=n-m;i++){
minn=min(minn,(presum[i+m]-presum[i]));
}
cout<<minn;
return 0;
}
二维前缀和


由于会涉及fij=fi-1j+fij-1-fi-1j-1+aij; 因此循环必须从1开始到n
模板 P1719
https://www.luogu.com.cn/problem/P1719

cpp
#include<bits/stdc++.h>
#include<limits.h>
int a[125][125],f[125][125];
using namespace std;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
}
}
int ans=INT_MIN;
for(int a=1;a<=n;a++){
for(int b=1;b<=n;b++){
for(int c=a;c<=n;c++){
for(int d=b;d<=n;d++){
int cur=f[c][d]+f[a-1][b-1]-f[c][b-1]-f[a-1][d];
ans=(ans>cur?ans:(cur));
}
}
}
}
cout<<ans<<endl;
return 0;
}
练习
P2004
https://www.luogu.com.cn/problem/P2004

cpp
#include<bits/stdc++.h>
#include<limits.h>
using namespace std;
int a[3000][3000],presum[3000][3000];
int main(){
int n,m,c;
cin>>n>>m>>c;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
presum[i][j]=a[i][j]-presum[i-1][j-1]+presum[i-1][j]+presum[i][j-1];
}
}
int val=INT_MIN;
int max=INT_MIN;
int cptx=1,cpty=1;
//就是这里全减去一了?
for(int i=1; i<=n-c+1; i++){
for(int j=1; j<=m-c+1; j++){
val = presum[i+c-1][j+c-1]
+ presum[i-1][j-1]
- presum[i-1][j+c-1]
- presum[i+c-1][j-1];
if(val>max){
cptx=i;
cpty=j;
max=val;
}
}
}
cout<<cptx<<" "<<cpty<<endl;
return 0;
}
差分(用于区间修改)
也可以求 区间内满足某个性质的数有多少个
方法一步:
坑前加 坑后补
cpp
for(int i=0;i<p;i++){
cin>>x>>y>>z;
diff[x]+=z;
//if(y+1<=n)
diff[y+1]-=z;
}
一维差分

差分和前缀和就是离散的积分和求导。
可以先差分后前缀和 也可以先前缀和再差分。

模板 P2367
https://www.luogu.com.cn/problem/P2367

差分是导数,所以要求变化后的数组需要计算变化量,变化量就是对导数积分,也就是对差分求前缀和。presum的值就是变化量,此时再把presum加回a数组即可得到变化后的数组。
cpp
for(int i=1;i<=n;i++){
presum[i]=presum[i-1]+diff[i];
a[i]+=presum[i];
min=(a[i]>min)?min:a[i];
}
cpp
#include<bits/stdc++.h>
#include<limits.h>
using namespace std;
int stu[5000005];
int diff[5000005]={0};
int presum[5000005]={0};
int n,p;
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++){
cin>>stu[i];
}
int x,y,z;
for(int i=0;i<p;i++){
cin>>x>>y>>z;
diff[x]+=z;
//if(y+1<=n)
diff[y+1]-=z;
}
int min=INT_MAX;
for(int i=1;i<=n;i++){
presum[i]=presum[i-1]+diff[i];
stu[i]+=presum[i];
min=(stu[i]>min)?min:stu[i];
}
cout<<min<<endl;
return 0;
}
练习
P3406
https://www.luogu.com.cn/problem/P3406
这题首先要看出是区间修改问题,然后要注意,得到的是城市的终点起点,而要累加的是路径,因此是比城市少一个的(两城市间一条路),所以在写差分时是start到end而不是end+1。

cpp
#include<bits/stdc++.h>
using namespace std;
long long diff[100005];
//要看出是区间修改问题,每次从任意两个站点走,因此中间段都要+1,是区间修改
int main(){
int N,M;
cin>>N>>M;
int p1,p2;
cin>>p2;
for(int i=1;i<M;i++){
cin>>p1;
int start =min(p1,p2),end=max(p1,p2);
diff[start]++,diff[end]--;//为什么是end就减一 而不是end+1
p2=p1;
}
long long a,b,c;
long long num=0,money=0;
for(int i=1;i<N;i++){
cin>>a>>b>>c;
num+=diff[i];//前缀和获得修改后的变化量,也就是各段乘坐的次数
money+=min(num*a,num*b+c);//注意买卡只需要买一次,后续都可以用b的价格坐
}
cout << money << endl;
return 0;
}
P2697
https://www.luogu.com.cn/problem/P2697

注意偏移下标是为了映射初次出现到负数域,如果不用偏移,那么下标恒为正无法映射负数。
偏移后必须同步拓展数组。
cpp
#include<bits/stdc++.h>
using namespace std;
int emerg[2000005];
int presum[2000005];
int OFFSET=1e6;
int main(){
memset(emerg,-1,sizeof(emerg));
string s;
cin>>s;
int max_len=0;
for(int i=1;i<=s.length();i++){
if(s[i-1]=='G') {
presum[i]=presum[i-1]+1;
}else{
presum[i]=presum[i-1]-1;
}
int idx=presum[i]+OFFSET;
if(presum[i]==0){//因为这一定是当前最长的串
max_len=i;
}
if(emerg[idx]!=-1){//说明是第二次出现
max_len=max(max_len,i-emerg[idx]);
}else{//说明是第一次出现
emerg[idx]=i;//设置为第一次出现的下标
}
}
cout<<max_len<<endl;
return 0;
}
二维差分

模板 P3397
https://www.luogu.com.cn/problem/P3397

cpp
#include<bits/stdc++.h>
using namespace std;
int arr[1005][1005];
int diff[1005][1005];
int presum[1005][1005];
int n,m;
int main(){
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c,d;
cin>>a>>b>>c>>d;
diff[a][b]++;
diff[c+1][d+1]++;
diff[a][d+1]--;
diff[c+1][b]--;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
presum[i][j]=diff[i][j]+presum[i-1][j]+presum[i][j-1]-presum[i-1][j-1];
arr[i][j]+=presum[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
练习
P2280
https://www.luogu.com.cn/problem/P2280

注意,设置目标的时候必须统一差分图和前缀和图,也就是tar和presum,既然presum是要求从1开始,而题目给定的坐标是从0开始,所以必须tarx+1y+1+=v;
cpp
#include<bits/stdc++.h>
using namespace std;
int n,m;
int presum[5005][5005];
int tar[5005][5005];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int x,y,v;
cin>>x>>y>>v;
tar[x+1][y+1]+=v;
}
for(int i=1;i<=5001;i++){
for(int j=1;j<=5001;j++){
presum[i][j]=presum[i][j-1]+presum[i-1][j]-presum[i-1][j-1]+tar[i][j];
}
}
int max_val=0;
m--;
for(int i=1;i<=5001-m;i++){
for(int j=1;j<=5001-m;j++){
int a,b,c,d;
a=i;b=j;c=i+m;d=j+m;
int cur_val=presum[c][d]+presum[a-1][b-1]-presum[c][b-1]-presum[a-1][d];
if(cur_val>max_val){
max_val=cur_val;
}
}
}
cout<<max_val<<endl;
}
大综合 二分查找+前缀和+差分
https://www.luogu.com.cn/problem/P1083
P1083

核心是输出需要修改订单的申请人编号,也就是第几个m不满足,是一个单点搜索,要用二分。
m是单调的,可以使用
二分对象就是订单编号
搜索范围是0到M+1
推荐条件是 avai是否能满足。
接下来看check函数怎么写?
也就是查看在m个需求的时候是否能满足,
条件是ri>需求
需求可以用前缀和得到。
先差分,然后前缀和得到变化量,也就是每天的需求,然后再用条件判断。
一定要注意要初始化init,否则前缀和和差分会用此前的记录。
cpp
#include<bits/stdc++.h>
using namespace std;
//nm在1e6 rd在1e9
long long presum[1000005];
int dst[1000005][3];
long long diff[1000005];
int ava[1000005];
int n,m;
int M;
void init(){
memset(diff,0,sizeof(diff));
memset(presum,0,sizeof(presum));
}
bool check(int M){
//M次请求 先差分
init();
for(int i=0;i<M;i++){
//从dst中取1 2 位
diff[dst[i][1]]+=dst[i][0];
diff[dst[i][2]+1]-=dst[i][0];
}
//然后前缀和
for(int i=1;i<=n;i++){
presum[i]=presum[i-1]+diff[i];
if(presum[i]>ava[i]) return false;
}
return true;
}
int find(){
long long L=0,R=m+1;
while(R>L){
int M=(L+R)/2;
if(check(M)){//如果M个订单足以满足 应该增加订单的数量
L=M+1;
}else{
R=M;
}
}
return L;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>ava[i];//存教室空位
}
for(int i=0;i<m;i++){
cin>>dst[i][0]>>dst[i][1]>>dst[i][2];
}
//先单独测一下m个订单是否都能满足
if(check(m)){
cout<<0<<endl;
}else{
int result=find();
cout<<-1<<endl<<result;
}
return 0;
}
P1314

注意是整数二分找到最接近s的W。
也就是找到刚好和s差不多的W。
然后测试左侧和右侧整数哪个更接近。