AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 797. 差分 【c++详细题解】    原题链接    简单

作者: 作者的头像   林小鹿 ,  2020-12-18 16:20:00 ,  所有人可见 ,  阅读 35989


878


521

差分


类似于数学中的求导和积分,差分可以看成前缀和的逆运算。

差分数组:

首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n];

然后我们构造一个数组b : b[1] ,b[2] , b[3],,,,,, b[i];

使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]

也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。

考虑如何构造差分b数组?

最为直接的方法

如下:

a[0 ]= 0;

b[1] = a[1] - a[0];

b[2] = a[2] - a[1];

b[3] =a [3] - a[2];

........

b[n] = a[n] - a[n-1];

图示:

20201217174809672.png

我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组 。

知道了差分数组有什么用呢? 别着急,慢慢往下看。

话说有这么一个问题:

给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c ,,,,,, a[r] + c;

暴力做法是for循环l到r区间,时间复杂度O(n),如果我们需要对原数组执行m次这样的操作,时间复杂度就会变成O(n*m)。有没有更高效的做法吗? 考虑差分做法。

始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。

首先让差分b数组中的 b[l] + c ,a数组变成 a[l] + c ,a[l+1] + c,,,,,, a[n] + c;

然后我们打个补丁,b[r+1] - c, a数组变成 a[r+1] - c,a[r+2] - c,,,,,,,a[n] - c;

为啥还要打个补丁?

我们画个图理解一下这个公式的由来:

20201215163431253.png
b[l] + c,效果使得a数组中 a[l]及以后的数都加上了c(红色部分),但我们只要求l到r区间加上c, 因此还需要执行 b[r+1] - c,让a数组中a[r+1]及往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生改变。

因此我们得出一维差分结论:给a数组中的[ l, r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c。时间复杂度为O(1), 大大提高了效率。

总结:
20201217172005485.png

AC代码

//差分 时间复杂度 o(m)
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];      //构建差分数组
    }
    int l, r, c;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;     //将序列中[l, r]之间的每个数都加上c
        b[r + 1] -= c;
    }
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];    //前缀和运算
        printf("%d ", a[i]);
    }
    return 0;
}

168 评论


用户头像
Kaisa   2022-09-24 22:32      96    踩      回复

感谢,比y总的容易理解(对于新手)

用户头像
i吃橘子   2022-10-30 13:01      8    踩      回复

雀氏

用户头像
山川故里   2023-01-04 14:43      5    踩      回复

同感

用户头像
怎得   2023-02-03 00:28      6    踩      回复

是这样的

用户头像
原神怎么了你   2023-02-19 15:15      13    踩      回复

比y总讲的好,一看就理解了

用户头像
云与月   2023-04-01 16:31      1    踩      回复

同感

用户头像
Love_cheng   11个月前    回复了 云与月 的评论      2    踩      回复

太有实力啦

用户头像
有空我就学算法   8个月前         踩      回复

借楼
n,p = map(int,input().split())
q = list(map(int,input().split()))
b = [0]*(n+2)
def insert(l,r,c):
global b
b[l] = b[l] +c
b[r+1] = b[r+1] - c
for i in range(n):
q[i] = int(q[i])
insert(i+1,i+1,q[i])
for i in range(p):
l,r,c = map(int,input().split())
insert(l,r,c)
cnt = 0
min1 = 101
for i in range(1,n+1):
cnt = cnt + b[i]
if cnt<min1:
min1 = cnt
print(min1)
洛谷上的题同差分数据为6e6,爆内存,c++可以但是python不行,我该怎么优化,求大佬帮忙

用户头像
不与世俗纷争   3个月前         踩      回复

确实比y总的容易理解一些


用户头像
Obscure   2021-11-08 21:00      39    踩      回复

随便记录点,希望对看不懂的有帮助

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];      //构建差分数组
    }
    //上面是构建差分数组,
    // b[1]=a[1]-a[0]
    // b[2]=a[2]-a[1]
    // b[3]=a[3]-a[2]
    // ·········
    // b[6]=a[6]-a[5];
    //目的是为了在前缀和中通过b里的某个元素修改后去修改a后面的值
    //构造了差分数组后就已经有了比如a[3]=b[3]+b[2]+b[1];这种前缀和关系。
    //只不过现在不用,因为为了去修改a的值我们还需要对中间的b进行修改,所以接下来
    //是先对b修改,再调用a[3]=b[3]+b[2]+b[1];
    int l, r, c;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;     //将序列中[l, r]之间的每个数都加上c
        b[r + 1] -= c;
    }


    //调用a[3]=b[3]+b[2]+b[1];这种前缀和运算。
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];    //前缀和运算
        printf("%d ", a[i]);
    }
    //前缀和运算a[3]=b[3]+b[2]+b[1] 因为前面已经构造了差分数组所以此时一定是成立的 //而且如果 中间的某个b不变的话那么a 就不会变。
    // a[3]=b[3]+b[2]+b[1] 
    //a[4]=b[4]+b[3]+b[2]+b[1]
    //他们可以直接写成这个样子
    //那么对中间的b[3] 修改了就是对a[3] 之后的值都修改了
    //这种。
    return 0;
}
用户头像
西皮皮膏体儿   2022-02-09 11:04      5    踩      回复

豁然开朗

用户头像
cjjj   2023-07-10 20:58      3    踩      回复

大佬好厉害,想问一下 a[N]写成全局变量,是因为会自动初始化为0 就不用初始化a[0]为0了嘛

用户头像
Louis_You   2023-07-17 16:38    回复了 cjjj 的评论      3    踩      回复

额

一般信竞选手都会把大部分数组设为全局定义

不过,你说得对。

用户头像
cjjj   12个月前    回复了 Louis_You 的评论      2    踩      回复

谢谢大佬~


用户头像
bryant_Lin   2022-07-11 13:57      10    踩      回复

简化了一下代码,减少了一个数组

#include<iostream>
using namespace std;

const int N = 1e5 + 10;

int n, m;
int b[N];

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r+1] -= c;
}

int main()
{
    cin >> n >> m;
    for(int i=1; i<=n; i++)
    {
        int x;
        cin >> x;
        insert(i, i, x);
    }

    while(m--)
    {
        int l, r, c;
        cin >> l >> r >> c;
        insert(l, r, c);
    }

    for(int i=1; i<=n; i++)
    {
        b[i] = b[i-1] + b[i];
        cout << b[i] << " ";
    }

    return 0;
}

用户头像
羊村村花   2023-01-16 12:23      4    踩      回复

差分跟多米诺骨牌一样,前一个和跟后一个有联系才能一个一个接着往前推


用户头像
5716   2022-05-05 12:23      3    踩      回复

赞


用户头像
笨死了   2023-03-20 12:36      2    踩      回复

学习总结了一、二维前缀和差分,希望对你有帮助~
https://blog.csdn.net/m0_74215326/article/details/129620912?spm=1001.2014.3001.5502

用户头像
awhiteboy   2023-03-27 21:50      1    踩      回复

tql


用户头像
Tomorrowland.   7天前      1    踩      回复

说实话,y总确实讲课水平不怎么样,评论区的大佬还是牛的


用户头像
不与世俗纷争   3个月前      1    踩      回复
 /*
            假设一开始所有原数组全为0,则差分数组也全部为0
            读取的每一个a[i](就是相当于在原数组的区间[i,i]的数都加上了一个a[i])
            就相当于是在差分数组中对应的b[i]位置上加上a[i],b[i+1]的位置上减去了a[i]
        */

用户头像
崔秀彬匿名女友   4个月前      1    踩      回复

谢谢太牛了!


用户头像
hfuu1   5个月前      1    踩      回复

感谢感谢


用户头像
伯纳多   8个月前      1    踩      回复

这下通透了!!!


用户头像
秋刀鱼_6   2023-05-09 16:24      1    踩      回复

a[i] = b[i] + a[i - 1]; //前缀和运算
假设L=1,r=10
看不懂这一步的:
i=1时 a[1]=b[1]+a[0]=a[1]+c
i=2时 a[2]=b[2]+a[1]=(a[2]-a[1])+a[1]+c=a[2]+c


用户头像
Pobz   2023-01-06 08:54      1    踩      回复

用户头像
羊村村花   2023-01-16 12:25         踩      回复

老哥怎么发图片的

用户头像
羊村村花   2023-01-16 12:27    回复了 羊村村花 的评论         踩      回复

1

用户头像
羊村村花   2023-01-16 12:27    回复了 羊村村花 的评论         踩      回复

懂了哈哈

![1](https://bqb12.bingping.top/Uploads/vod/2018-08-07/5b6870eb6d191.jpg)
用户头像
Pobz   2023-01-16 12:31    回复了 羊村村花 的评论         踩      回复

$\color{Magenta}{hhh,我也才学的makedown语言}$

用户头像
salary   2023-02-13 22:11    回复了 羊村村花 的评论         踩      回复

1

用户头像
原神怎么了你   2023-02-19 15:17    回复了 salary 的评论      1    踩      回复

1

用户头像
pdsujc06   6个月前    回复了 羊村村花 的评论         踩      回复
#好高级
太酷了

这是什么

用户头像
pdsujc06   6个月前    回复了 pdsujc06 的评论         踩      回复


用户头像
Javaer   2022-12-18 01:35      1    踩      回复

感谢林子哥 解决了我2h的困扰


用户头像
懒惰的蚩蚩   2022-07-21 09:46      1    踩      回复

题解写得真好!!


用户头像
FXW   2022-06-16 09:20      1    踩      回复

大佬,你给的代码运行后输出的结果中最后一位数与标准答案不一样呢,


用户头像
evil_7   2022-05-15 09:36      1    踩      回复

大佬在人间


用户头像
Newoahil   2022-04-27 18:35      1    踩      回复

Orz


用户头像
DongTang   2022-04-22 01:22      1    踩      回复

提醒一下,如果开辟的是大小为n + 1的动态数组的话,在
while (m–)
{
scanf(“%d%d%d”, &l, &r, &c);
b[l] += c; //将序列中[l, r]之间的每个数都加上c
b[r + 1] -= c;
}
这里面,要对b[r + 1] -= c;进行判断if(r < n),否则会出现数组越界


用户头像
湘圆琴子-   2021-10-10 18:03      1    踩      回复

太赞了。y总第第二个for循环我硬是没看懂啥意思,你的代码我看下就理解了!!!!


你确定删除吗?

© 2018-2024 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息