子区间求和的那些事儿(下)——差分详解

0.前情提要

不好意思托更了辣木久…

上一回,TengMax君讲了简单的子区间求和,

运用了一维预处理解决了题。

最终布置了一个练习

1.习题讲解

便是

洛谷U97755 【模板】前缀和

U97755 【模板】前缀和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

前缀和应该没有什么大碍(详见子区间求和上);

问题是前缀异或

然而,这里我们需要运用异或的两个性质,
a ⨁ a = 0 a \bigoplus a=0 aa=0

0 ⨁ a = a 0 \bigoplus a=a 0a=a

我们用前缀和的方法,

所以 [ l , r ] [l,r] [l,r]异或的结果为
d i r [ r ] ⨁ d i r [ l − 1 ] dir[r] \bigoplus dir[l - 1] dir[r]dir[l1]
所以代码为

#include <bits/stdc++.h>
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define ll long long

using namespace std;

ll n, m;
ll a[2000005];
ll ts[2000005];
ll ty[2000005];

int main() {
	scanf("%lld%lld", &n, &m);
	for (register ll i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		ts[i] = ts[i - 1] + a[i];
		ty[i] = ty[i - 1] ^ a[i];
	}
	while (m--) {
		ll type;
		ll a1, a2;
		scanf("%lld%lld%lld", &type, &a1, &a2);
		if (type == 1) {
			printf("%lld\n", ts[a2] - ts[a1 - 1]);
		}
		else {
			printf("%lld\n", (ty[a2] ^ ty[a1 - 1]));
		}
	}
	return 0;
}

在这里插入图片描述

过啦~

2.正题引入

今天要TengMax君讲的与前缀和脱不了干系,

就是前缀和的逆运算——

差分

3.数学定义

差分到底是什么意思?

我们假设有一个原数组
a [ M a x n ] : i n t a[Maxn]:int a[Maxn]:int
将它进行差分,得到了蜜(谜)汁(之)数组

再将蜜(谜)汁(之)数组进行一次前缀和,

这次的前缀和数组,就是原数组

而这个差分,就是要解决进行一番修改之后

子区间求和

4.代码以及解释

那这个进行一番修改之后的子区间求和,

到底是怎么修改?

首先有一个原数组:
a [ M a x n ] : i n t a[Maxn]:int a[Maxn]:int

接着有一个操作数 m m m

接下来 m m m个操作里

每个操作有3个数 [ l , r , x ] [l, r, x] [l,r,x]

代表 a 数组从 l 到 r 的所有的数加 x a数组从l到r的所有的数加x a数组从lr的所有的数加x

最后又有 q q q个询问数,

接着就与上一回的题目一样了

首先,最能想到的就是暴力操作+前缀和

解法一

那就是这样:

void ArrSum() {
    int n;
    cin >> n;
    int a[n + 1];
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    int q, p;
    cin >> p;
    while (p--) {
        int x, y, z;
        cin >> x >> y >> z;
        for (int i = x; i <= y; i++) {
            a[i] += z;
        }
    }
    for (int i = 1; i <= n; i++) {
        a[i] += a[i - 1];
    }
    cin >> q;
    while (q--) {
        int x, y;
        cin >> x >> y;
        cout << a[y] - a[x - 1] << endl;
    }
}

呃……

时间复杂度 O ( n p ) O(np) O(np)
空间复杂度 O ( n ) O(n) O(n)

好像哪里有点不太对,不妨

优化一下

我真的会谢

不过在此之前,咱先把蜜(谜)汁(之)数组是啥解决了来

插:差分实现

我们不妨假设,差分数组
d [ M a x n ] : i n t d[Maxn]:int d[Maxn]:int
的值为
d = { 1 , 1 , 1 , 1 , 1 } d=\{1,1,1,1,1\} d={1,1,1,1,1}

根据差分的定义,写出原数组
a = { 1 , 2 , 3 , 4 , 5 } a=\{1,2,3,4,5\} a={1,2,3,4,5}
你关注这个干啥

所以 d 和 a d和a da的关系就是
d i = a i − a i − 1 d_i=a_i-a_{i-1} di=aiai1
我们再动动 a a a数组,看看会咋样

比如我们将 [ 2 , 4 ] [2,4] [2,4]区间的内容全部加 4 4 4

所以,
a 1 = { 1 , 6 , 7 , 8 , 5 } a1=\{1,6,7,8,5\} a1={1,6,7,8,5}
根据我们推导的公式得出差分数组
d 1 = { 1 , 5 , 1 , 1 , − 3 } d1=\{1,5,1,1,-3\} d1={1,5,1,1,3}
相比于 d d d数组 d 1 d1 d1数组只是 2 的位置加 4 2的位置加4 2的位置加4 5 的位置减 4 5的位置减4 5的位置减4

现在咱就找着了进行一番修改之后的子区间求和

这张图确实不咋滴我跟你说

那我们再来回顾例题

解法二

根据我们推导的公式
可以马上写出代码:

void ArrSum() {
    int n;
    cin >> n;
    int a[n + 1], d[n + 1];
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        d[i] = a[i] - a[i - 1];
    }
    int q, p;
    cin >> p;
    while (p--) {
        int x, y, z;
        cin >> x >> y >> z;
        d[x] += z;//利用查分数组进行区间修改
        d[y + 1] -= z;
    }
    for (int i = 1; i <= n; i++) {
        d[i] += d[i - 1];
    }//执行完这个循环后d就是原数组
    for (int i = 1; i <= n; i++) {
        d[i] += d[i - 1];
    }//执行完这个循环后d就是前缀和数组
    cin >> q;
    while (q--) {
        int x, y;
        cin >> x >> y;
        cout << d[y] - d[x - 1] << endl;
    }
}
时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( n ) O(n) O(n)

嗯,不愧是我

5.练习

嗯,就它了

P2367 语文成绩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

See You

欲知后事如何,且听下回分解

😛

The End

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值