前缀和与差分:从一维到二维(上)

在这里插入图片描述

前缀和

概念引入

众所周知,当你要查询区间和时,是不是要写这样一串代码:

int sum=0;
for(int i=a;i<=b;i++){
    sum+=a[i];
}

而这样的方法时间复杂度为 O ( b − a ) O(b-a) O(ba)

经过多次查询,就会变得非常缓慢;

有没有一种快捷高效的方法呢?

当然是有的:那就是前缀和;

假设: b [ i ] = a [ 1 ] + a [ 2 ] + a [ 3 ] + . . . . . . a [ i ] b[i]=a[1]+a[2]+a[3]+......a[i] b[i]=a[1]+a[2]+a[3]+......a[i]

则a[i]至a[j]的区间和为 b [ j ] − b [ i ] b[j]-b[i] b[j]b[i]
sum[j]-sum[i]...好像打错了,不管他吧
sum[j]-sum[i]…好像打错了,不管他吧

这样只需要的一次sum数组的赋值预算,就可以进行查询了

代码如下:

for(int i=1;i<=k;i++){
    sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=q;i++){
    int m,n;
    cin>>m>>n;
    cout<<sum[n]-sum[m]<<endl;
}

而用这样的方法,就可以在做类似查询最大区间和的题的时候把 O ( n 3 ) O(n^3) O(n3)的复杂度降低到 O ( n 2 ) O(n^2) O(n2),不知道快了多少。

习题

最大连续子序列和

题目描述

给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。

例如:

序列(-2,11,-4,13,-5,-2)的最大子序列和为20

序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0,如果小于0,其结果为0。

输入格式

第一行输入一个整数n 第二行输入n个用空格分开的整数 a i a_i ai

输出格式

输出一个整数,表示这个序列的最大连续子序列的和

样例

样例输入1:
6
-2 11 -4 13 -5 -2
样例输出1:
20
样例输入2:
12
-6 2 4 -7 5 3 2 -1 6 -9 10 -2
样例输出2:
16
这种时候,前缀和就有他的用武之地了

#include<bits/stdc++.h>
using namespace std;
const int M=1e7+5;
int a[M];
long long sum[M],ms;
int main(){
    ios::sync_with_stdio(false);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){
        sum[i]=a[i]+sum[i-1];//统计前缀和
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
                ms=max(sum[j]-sum[i],ms);
        }
    }
    cout<<ms;
}

差分

概念引入

差分跟前缀和在本质上是一样的
为了好理解,我们打个比方
减是加的逆运算
乘是除的逆运算
开方是乘方的逆运算
那么,差分就是前缀和的逆运算
众所周知,当你要区间加减时,是不是要写这样一串代码:

cin>>c;
for(int i=a;i<=b;i++){
    a[i]+=c;
}

而这样的方法时间复杂度为 O ( b − a ) O(b-a) O(ba)
这样还是太复杂了
而什么是差分呢
假设有两个数组 a 和 b a和b ab
b b b的前缀和是 a a a
我们则称 b b b a a a的差分
a [ i ] = b [ 1 ] + b [ 2 ] + b [ 3 ] + . . . . . . b [ i ] a[i]=b[1]+b[2]+b[3]+......b[i] a[i]=b[1]+b[2]+b[3]+......b[i]
在这里插入图片描述
而差分就可以帮我们进行简单的区间修改(感谢大佬的图)
如果我们把 d 4 d_4 d4加上 m m m
因为 a a a d d d的前缀和,所以 a 4 a_4 a4 a 11 a_{11} a11都将加上 m m m
可我们要的是 a 4 a_4 a4 a 8 a_8 a8修改,怎么把后面的也改了?
这时候,我们就要在 d 9 d_9 d9减少对应的 m m m
因为 a a a d d d的前缀和,所以 a 9 a_9 a9 a 11 a_{11} a11都将减少 m m m
这样就能在 O ( 1 ) O(1) O(1)的复杂度上进行区间修改

习题

差分

题目描述

输入一个长度为 n n n的整数序列。

接下来输入 m m m个操作,每个操作包含三个整数 l , r , c l,r,c l,r,c
表示将序列中 [ l , r ] [l,r] [l,r]之间的每个数加上 c c c
请你输出进行完所有操作后的序列。

输入格式

第一行包含两个整数 n n n m m m

第二行包含n个整数,表示整数序列。
接下来 行,每行包含三个整数 l , r , c l,r,c l,r,c
表示一个操作。

输出格式

共一行,包含 n n n个整数,表示最终序列。

样例

输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2

数据范围与提示

1 ≤ n , m ≤ 100000 1≤n,m≤100000 1n,m100000
1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn
− 1000 ≤ c ≤ 1000 -1000≤c≤1000 1000c1000
− 1000 ≤ a i ≤ 1000 -1000≤a_i≤1000 1000ai1000
这个时候,如果暴力求解,肯定超时,就只能用差分了

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100005],b[100005];
int main()
{
    cin>>n>>m;
	for(int i=1;i<=n;i++){
        cin>>a[i];
        b[i]=a[i]-a[i-1];
    }
    for(int i=1;i<=m;i++){
        int l,r,c;
        cin>>l>>r>>c;
        b[l]+=c;
        b[r+1]-=c;
    }
    for(int i=1;i<=n;i++){
        b[i]+=b[i-1];
		cout<<b[i]<<" "; 
    }
	return 0;
}

感谢大家的观看,下次我会给大家带来更有趣的二维前缀和与差分。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值