算法|基础算法|前缀和和差分

数学与数论|逆元

1.一维前缀和
2.二维前缀和
3.一维差分
4.二维差分

心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。
在这里插入图片描述
注意前缀和约定俗成的规矩:下标从1开始(方便)

一维前缀和,一维前缀和是一种能快速求出区间的和的算法
sum[i] : 前i个数的和

 vector<int> a(n+1),sum(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    //sum[i]=a[1]+a[2]...a[i]

sum[L,R]:区间[L,R]的和
sum[L,R] = sum[R] - sum[L-1]
暴力求区间和:O(n) n为区间长度
前缀和:O(1)求出区间[L,R]的和

暴力:

for(int i=l;i<=r;i++){
        sum+=x;
    }

一维前缀和

题目描述
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10

实践代码:

void solve(){
    int n,m;cin>>n>>m;
    vector<int> a(n+1,0),sum(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];//前缀和处理
    while(m--){//询问次数
        int l,r;
        cin>>l>>r;
        cout<<sum[r]-sum[l-1]<<endl;
    }
}

二维前缀和,二维前缀和是一种能快速求出矩形区域中的和
sum[i][j]:代表(i,j)点左上角矩形区域的面积
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j]

 for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] +a[i][j];
        }
    }

暴力求矩形和:O(n*m) n * m为矩形大小
二维前缀和:O(1)

二维前缀和

题目描述
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。
输出格式
共q行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例
17
27
21

实践代码:

const int N =1010;
int a[N][N],sum[N][N];
int query(int l1,int r1,int l2,int r2){//子矩阵和函数
    return sum[l2][r2]-sum[l1-1][r2]-sum[l2][r1-1]+sum[l1-1][r1-1];
}
void solve(){
    int n,m,q;cin>>n>>m>>q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    /*前缀和处理*/
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] +a[i][j];
        }
    }
    while(q--){//询问次数
        int l1,r1,l2,r2;cin>>l1>>r1>>l2>>r2;
        cout<<query(l1,r1,l2,r2)<<endl;
    }
}

差分,差分是一种和前缀和相对的策略,可以当作是求和的逆运算
原数组a: 1 2 3 4 5
构造差分数组:b[1]=a[2]-a[1]=1,b[2]=a[3]-a[2]=1…
差分数组b: 1 1 1 1 1
为什么说差分数组是前缀和的逆运算?
将b数组求和得到 1 2 3 4 5,所以说差分是前缀和的逆运算,通过差分将区间[L,R]整体 +c <==> b[L]+=c b[R+1]-=c

在这里插入图片描述

一维差分一般用于求解将区间[L,R]整体+c的操作
1.先求出差分数组
2.b[L]+=c
3.b[R+1]-=c

一维差分

题目描述
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
输出格式
共一行,包含 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≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000

实践代码

void solve(){
    int n,m;cin>>n>>m;
    vector<int> a(n+1),b(n+1),sum(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1];
    while(m--){
        int l,r,c;cin>>l>>r>>c;
        b[l]+=c;
        b[r+1]-=c;
    }
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i];
    for(auto x:sum) cout<<x<<' ';
}

二维差分一般用于求解将矩形区域整体 +c 的操作
1.二维前缀和定义:
sum[i][j]:代表(i,j)点左上角矩形的区域的面积
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j]
2.经过定义得到的二维差分的差分数组
a[i][j] = sum[i][j] - sum[i-1][j]-sum[i][j-1] + sum[i-1][j-1]
b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
(sum是a的前缀和,那么a即是sum的差分,同理b也是a的差分,那么a就是b的前缀和)
3.通过二维差分将矩形区域整体 +c <==> 将矩形区域(x1,y1,x2,y2) 整体 +c
(差分数组修改b[i][j]时会对a[i][j]及以后的每一个数都有影响(改一个数,前缀和肯定变啊!))

二维差分

题目描述
输入一个 n 行 m列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例
2 3 4 1
4 3 4 1
2 2 2 2

实践代码

const int N = 1010;
int a[N][N],b[N][N],c[N][N];
void solve(){
    int n,m,q;cin>>n>>m>>q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            b[i][j] = a[i][j] - a[i-1][j]-a[i][j-1] + a[i-1][j-1];//差分数组
        }
    }
    while(q--){
        int l1,r1,l2,r2,c;cin>>l1>>r1>>l2>>r2>>c;
        b[l1][r1]+=c;
        b[l1][r2+1]-=c;
        b[l2+1][r1]-=c;
        b[l2+1][r2+1]+=c;//多减了一遍c,所以要再加上
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            c[i][j] = c[i-1][j]+c[i][j-1]-c[i-1][j-1]+b[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cout<<c[i][j]<<' ';
        }
        cout<<endl;
    }
}

心有猛虎,细嗅蔷薇。再见了朋友~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值