一篇带你速通差分算法(C/C++)

一篇带你速通差分算法(C/C++)


个人主页:摆烂小白敲代码

创作领域:算法、C/C++

持续更新算法领域的文章,让博主在您的算法之路上祝您一臂之力

欢迎各位大佬莅临我的博客,您的关注、点赞、收藏、评论是我持续创作最大的动力

差分算法是一种在计算机科学中常用的算法,特别是在处理序列数据时,它可以帮助我们快速计算出序列中相邻元素的差值。时间复杂度可以达到O(1),在C++中实现差分算法不仅可以提高程序的效率,还可以简化代码的复杂度。本文将详细介绍差分算法的原理、C++实现方法以及算法例题。 

算法原理

上一篇博客一篇带你速通前缀和算法(C/C++)-ZEEKLOG博客我们介绍了前缀和算法,这一篇文章就与前缀和算法相反为差分算法。

一维差分:

差分算法的核心思想是利用已有的数据序列,通过计算相邻元素之间的差值来生成一个新的序列。对于一个给定的序列 a=[a1​,a2​,...,an​],其差分序列 d 可以表示为:d[i]=a[i]−a[i-1]。差分数组长度一般为原定序列长度+1,即:len=n+1。其中,d[0] 通常被定义为0,或者根据具体应用场景进行特殊处理。我们设原数组序列为a=[3,1,5,4,2],下标从1开始,那么差分数组可以由d[i]=a[i]−a[i-1]求得,如下图所示。

一维差分在修改区间时效率非常高,时间复杂度可以达到 O(1),我们通过对差分数组的修改以达到修改原数组的目的,那么如何修改差分数组,比如在数组a的[l,r]区间上的数统一+c,转化为差分数组为d[l]+c,d[r+1]-c,这样我们再利用前缀和还原便可以得到原数组修改后的值。在还原时,只需要将前i位差分数组相加便可以得到原数组,比如还原第三位,a[3]=d[1]+d[2]+d[3],便可以还原其值,其还原的第n+1位一定是0。

我们设原数组序列为 a=[3,1,5,4,2],设d为差分数组,在[2,4]区间上的值统一+2,下面我们将模拟实现。

初始化:

差分实现: 

我们需要在[2,4]区间上的值统一+2,那么转换为差分数组d[l]+c,d[r+1]-c==d[2]+2,d[4+1]-2。我们代入到过程实现一下。

 原数组序列为 a=[3,1,5,4,2],在[2,4]区间上的值统一+2,我们将得到a=[3,3,7,6,2],与差分还原的来是一样的。


二维差分:

前面我们讲述的是一维差分,其数组为一维的,其模型可以抽象为线性的。那么有些时候需要我们使用二维差分,当题目出现矩阵时,说明就涉及到了二维差分,其模型可以抽象为矩阵,二维差分稍微比一维差分难一点,主要是在处理区间更新时。我们设d[i][j]为(1,1)点到(i,j)点的差分子矩阵(1<=x<=i,1<=y<=j),此时我们设a[i][j]=Σd[i][j](1<=x<=i,1<=y<=j),即a[i][j]=差分矩阵的和。

构造差分矩阵:

当我们需要构造差分数组实现修改区间的值时,此时的递推关系式不再是跟一维差分相同,由于是二维的,所以转化为在一个矩阵上+c,在一个矩阵上-c,那么如何确定这两个矩阵。假设我们在(x1,y1)->(x2,y2)这个矩阵统一加C,那么转化为差分矩阵的操作为

d[x1][y1]+C d[x1][y2+1]-C d[x2+1][y1]-C d[x2+1][y2+1]+C //上面操作等价于 for(int i=x1;i<=x2;i++) for(int j=y1;j<=y2;j++) a[i][j]+=C;

 在构造差分矩阵时,可以先初始化一个差分矩阵都为0,把自己的点,(x,y)->(x,y)插入到差分矩阵中,代码如下

void insert(int x1, int y1, int x2, int y2, int c)//构造差分矩阵 { b[x1][y1] += c; b[x2 + 1][y1] -= c; b[x1][y2 +1] -= c; b[x2 +1][y2+1] +=c; }
构造差分矩阵解释 :

d[x1][y1]+C相当于原矩阵A矩阵(黄色)每个值都+C

d[x2+1][y2+1]+C相当于整个矩阵A+B+C+D矩阵每个值都+C

此时A矩阵+2C,B矩阵+C,C矩阵+C,D矩阵+C,我们再设法把不需要的减掉。

d[x1][y2+1]-C相当于A+B矩阵(黄色+绿色)都-C

此时A矩阵+C,B矩阵+0,C矩阵+C,D矩阵+C,我们再设法把不需要的减掉。

d[x2+1][y1]-C相当于A+C矩阵(黄色+蓝色)都-C

此时A矩阵+0,B矩阵+0,C矩阵+0,D矩阵+C,达到了我们所需要的(x1,y1)到(x2,y2)加C的目的。

最后就是还原其值,利用前缀和的思想,如上图所示,要求(1,1)->(x2,y2)的值,那么可以转化为矩阵(1,1)->(x2,y2-1)与(1,1)->(x2-1,y2)的和减去(1,1)->(x2-1,y2-1)矩阵的值,最后再加上自身值d[x2][y2]。其递归式为d[i][j] += d[i - 1][j] + d[i][j - 1] - d[i - 1][j - 1]。

for (int i = 1; i <= n; i++)//二维前缀和还原 { for (int j = 1; j <= m; j++) { d[i][j] += d[i - 1][j] + d[i][j - 1] - d[i - 1][j - 1]; } }
代码实现:
#include<iostream> using namespace std; const int N =1010; int a[N][N],d[N][N]; int n,q; void insert(int x1, int y1, int x2, int y2, int c) { d[x1][y1] += c; d[x2 + 1][y1] -= c; d[x1][y2 +1] -= c; d[x2 +1][y2+1] +=c; } int main() { scanf("%d%d", &n,&q); for(int i = 1;i <= n; i++){ for(int j = 1;j <= n; j++){ cin >> a[i][j]; insert(i, j, i, j, a[i][j]); } } while( q-- ) { int x1, x2, y1, y2,c; cin >> x1 >> y1>> x2 >> y2 >> c ; insert(x1 ,y1, x2, y2, c);//修改区间 } //求前缀和 for(int i = 1; i<=n; i++){ for(int j = 1; j<=n; j++){ d[i][j] += d[i][j-1] + d[i-1][j] - d[i-1][j-1]; printf("%d ", d[i][j]); } cout<<endl; } return 0; } 

算法例题 

AcWing 4262. 空调

Farmer John 的 N 头奶牛对他们牛棚的室温非常挑剔。

有些奶牛喜欢温度低一些,而有些奶牛则喜欢温度高一些。

Farmer John 的牛棚包含一排 N 个牛栏,编号为 1…N,每个牛栏里有一头牛。

第 i 头奶牛希望她的牛栏中的温度是 pi,而现在她的牛栏中的温度是 ti。

为了确保每头奶牛都感到舒适,Farmer John 安装了一个新的空调系统。

该系统进行控制的方式非常有趣,他可以向系统发送命令,告诉它将一组连续的牛栏内的温度升高或降低 1 个单位——例如「将牛栏 5…8 的温度升高 1 个单位」。

一组连续的牛栏最短可以仅包含一个牛栏。

请帮助 Farmer John 求出他需要向新的空调系统发送的命令的最小数量,使得每头奶牛的牛栏都处于其中的奶牛的理想温度。

输入格式

输入的第一行包含 N。

下一行包含 N 个非负整数 p1…pN,用空格分隔。

最后一行包含 N 个非负整数 t1…tN。

输出格式

输出一个整数,为 Farmer John 需要使用的最小指令数量。

数据范围

1≤N≤10^5
0≤pi,ti≤10000

输入样例:

5 1 5 3 3 4 1 2 2 2 1 

输出样例:

5 

样例解释

一组最优的 Farmer John 可以使用的指令如下:

初始温度 :1 2 2 2 1 升高牛棚 2..5:1 3 3 3 2 升高牛棚 2..5:1 4 4 4 3 升高牛棚 2..5:1 5 5 5 4 降低牛棚 3..4:1 5 4 4 4 降低牛棚 3..4:1 5 3 3 4

解题思路:

一个数组转化到另一个数组,我们可以考虑利用差分,快速解决,在一个区间上所有的数加上一个数k或者减去一个数k,我们可以等价转化为差分数组在区间两端一个+k一个-k。题目中起始数组到目标数组,我们可以把这两个数组做差值,然后差值数组变为差分数组,等价为转化到全零数组。差分数组每个值加和必为0(此题必有解),因为我们每次只相当于在差分数组两个值上加减,一个加一个减,要到达0数组,那肯定小于0的每次+1,直到为0,大于0的每次-1,直到为0。

p数组:   1 5 3 3 4 t数组:   1 2 2 2 1 差值数组:0 3 1 1 3 差分数组:0 3 -2 0 2 -3 第一次:  0 2 -1 0 2 -3 第二次:  0 1  0 0 2 -3 第三次:  0 0  0 0 2 -2 第四次:  0 0  0 0 1 -1 第五次:  0 0  0 0 0  0 所以只需要统计大于0或者小于0的值即可

AC代码:
#include<iostream> using namespace std; const int N=1e5+5; int n,ans; int p[N],t[N];//p数组既作为p数组又作为差分数组 int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>p[i]; } for(int i=1;i<=n;i++){ cin>>t[i]; p[i]-=t[i]; } //差分p[i]=p[i]-p[i-1],差分数组边界p[1]=p[1],p[n+1]=-p[n] for(int i=n+1;i>=1;i--){//因为i要先于i-1更新,所以逆序遍历 p[i]-=p[i-1]; } for(int i=1;i<=n+1;i++){//只要遍历大于0或者小于零的p[i]累加即可 if(p[i]>0){ ans+=p[i]; } } cout<<ans<<endl; return 0; }

AcWing 4862. 浇花

某公司养有观赏花,这些花十分娇贵,每天都需要且仅需要浇水一次。

如果某一天没给花浇水或者给花浇水超过一次,花就会在那一天死亡。

公司即将迎来 n 天假期,编号 1∼n。

为了让花能够活过整个假期,公司领导安排了 m 个人(编号 1∼m)来公司浇花,其中第 i 个人在第 [ai,bi] 天每天来公司浇一次花。

领导是按照时间顺序安排的浇花任务,保证了对于 1≤i≤m−1,均满足:bi≤ai+1。

给定领导的具体安排,请你判断,花能否活过整个假期,如果不能,请你输出它是在第几天死的,以及那一天的具体浇水次数。

输入格式

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

接下来 m 行,每行包含两个整数 ai,bi。

输出格式

输出一行结果。

如果花能活过整个假期,则输出 OK

如果花不能活过整个假期,则输出两个整数 x,y,表示花是在第 x 天死的,这一天花被浇了 y 次水。

数据范围

前 4 个测试点满足 1≤n,m≤10。
所有测试点满足 1≤n,m≤10^5,1≤ai≤bi≤n。

输入样例1:

10 5 1 2 3 3 4 6 7 7 8 10 

输出样例1:

OK 

输入样例2:

10 5 1 2 2 3 4 5 7 8 9 10 

输出样例2:

2 2 

输入样例3:

10 5 1 2 3 3 5 7 7 7 7 10 

输出样例3:

4 0

解题思路:

这道题其实一眼看上去是区间合并问题,这里我们讲解的差分算法,那么我们就用差分解决此题。员工在一个区间内浇水那么就可以视为区间修改,可以构造差分数组快速解决,这道题还一个条件就是花不能多浇水,这个实现也可以在差分数组之中,我们可以利用前缀和还原差分数组得到此朵花被浇了几次水,进而判断此花是否存活。


AC代码:
#include<iostream> using namespace std; const int N = 1e5 + 5; int n,m; int b[N]; int main(){ cin >> n >> m; while(m--){ int l,r; cin >> l >> r; b[l]++;//构造差分数组 b[r + 1]--; } for(int i = 1;i <= n;i++){ b[i] = b[i] + b[i - 1];//前缀和还原 if(b[i] > 1 || b[i] < 1) {//超过一次浇水或者一次也没有浇水 cout << i << ' ' << b[i] << endl; return 0; } } cout << "OK" << endl; return 0; }

AcWing 503. 借教室

在大学期间,经常需要租借教室。

大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。

教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来 n 天的借教室信息,其中第 i 天学校有 ri 个教室可供租借。

共有 m 份订单,每份订单用三个正整数描述,分别为 dj,sj,tj,表示某租借者需要从第 sj 天到第 tj 天租借教室(包括第 sj 天和第 tj 天),每天需要租借 dj 个教室。

我们假定,租借者对教室的大小、地点没有要求。

即对于每份订单,我们只需要每天提供 dj 个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。

如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。

这里的无法满足指从第 sj 天到第 tj 天中有至少一天剩余的教室数量不足 dj 个。

现在我们需要知道,是否会有订单无法完全满足。

如果有,需要通知哪一个申请人修改订单。

输入格式

第一行包含两个正整数 n,m,表示天数和订单的数量。

第二行包含 n 个正整数,其中第 i 个数为 ri,表示第 i 天可用于租借的教室数量。

接下来有 m 行,每行包含三个正整数 dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。

每行相邻的两个数之间均用一个空格隔开。

天数与订单均用从 1 开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数 0。

否则(订单无法完全满足)输出两行,第一行输出一个负整数 −1,第二行输出需要修改订单的申请人编号。

数据范围

1≤n,m≤10^6
0≤ri,dj≤10^9
1≤sj≤tj≤n

输入样例:

4 3 2 5 4 3 2 1 3 3 2 4 4 2 4 

输出样例:

-1 2

解题思路:

此题可用差分+二分或者线段树来解,由于博主能力限制这里会由易懂的差分+二分来讲解。这道题第一感觉应该就是差分了吧,修改区间的值,用差分效率更高一点。我们可以把申请人的需要转化成差分数组,利用前缀和还原,判断i个教室是否满足。如果我们只依靠差分是解决不了此题的,只利用差分时间复杂度为O(nm),大约10^12,肯定会超时,那么我们可以考虑把内层循环利用二分优化掉,具体咋优化呢,我们有了一个申请人编号的差分数组,从第一位申请单到最后一个申请单,越在前面的越容易借到教室,说明教室剩余够多,越到后面剩余可借教室会减少,因为会被前面的申请借去,那就说明第一个不满足的编号越往后概率越高,这样是不是感觉像是一个有序的序列,从头到尾,能满足的概率是由大到小的。那么我们利用二分去查到最后一个能满足的订单,+1即为不能满足的订单,上代码。


AC代码:
#include<iostream> #include<cstring> using namespace std; typedef long long LL; const int N=1e6+5; int n,m; LL r[N],d[N],s[N],t[N],c[N]; bool cheak(int mid){//判断第mid个申请是否可以满足 memset(c,0,sizeof(c));//每次都会有判断,防止上次的数据影响本次的判断 for(int i=1;i<=mid;i++){//采用申请人由0到差分数组,所以直接差分操作即可 c[s[i]]+=d[i]; c[t[i]+1]-=d[i]; } int ans=0; for(int i=1;i<=n;i++){//求前缀和,即还原数组 c[i]+=c[i-1]; if(c[i]>r[i]){//如果需要的教室大于所能提供的教室,则返回false return false; } } return true; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>r[i]; } for(int i=1;i<=m;i++){ cin>>d[i]>>s[i]>>t[i]; } int l=0,r=m; while(l<r){//实行二分查找最大能满足的申请人编号 int mid=l+r+1>>1;//向上取整 if(cheak(mid))l=mid; else r=mid-1; } if(r==m){//如果最大满足的编号等于m,说明所有订单均可满足 cout<<0<<endl; }else{ cout<<-1<<endl<<r+1<<endl;//前面定义的为能满足的最大编号,r+1即为第一个不能满足的编号 } return 0; }

由此篇可见差分还是比较重要的,区间修改算法的效率也是非常高的,在算法竞赛中比较重要,希望对大家有所帮助,文章有错误的地方,恳请各位大佬指出。执笔至此,感触彼多,全文将至,落笔为终,感谢大家的支持。 

Read more

解放双手,让 AI 帮咱审查代码——GitLab 智能审查实战指南

unsetunset一、题记unsetunset 你是不是有如下这些困扰? 困惑1:代码审查效率低下。 团队成员提交的 MR(Merge Request)堆积如山,人工审查耗时耗力,经常因为时间紧迫而草草通过? 困惑2:代码质量参差不齐。 新人代码风格不统一,老手也难免疏忽,潜在的安全风险和性能问题常常在上线后才暴露?  曾几何时,13年前作为新入职员工,每隔几个月都要参加公司的代码规范考试,考不过要补考。补考不过再补考,直到考过为止...... 困惑3:审查标准难以统一。 不同审查者的关注点不同,缺乏统一的审查标准,导致代码质量波动较大? 如果你深受以上痛点困扰,那么本文将为你带来一套完整的解决方案——通过 GitLab 与 Coco AI 的深度集成,实现智能化、自动化的代码审查流程。 让 AI 助手在每次代码提交时自动检测代码风格问题、安全漏洞和潜在 bug,为你的代码质量把好第一道关。 unsetunset二、完整实现步骤unsetunset 2.0 前置条件 * 1、

By Ne0inhk

保姆级教程:Windows本地部署Ollama+OpenClaw,打造你的AI赚钱系统(APP开发/量化/小说/剪辑)

摘要:想用AI搞钱但卡在技术门槛?本文手把手教你用一台Windows电脑,零成本本地部署Ollama大模型+OpenClaw智能中枢,赋予AI开发APP、量化分析、编写小说、剪辑辅助等“赚钱技能”。全程无需编程基础,跟着鼠标点、照着命令敲,即可拥有24小时待命的AI员工。 一、写在前面 很多朋友对AI变现跃跃欲试,却常被这些问题劝退: * 云端部署太贵,API调用怕浪费钱 * 技术文档看不懂,不知道从哪下手 * 数据隐私担忧,不敢把敏感资料上传 其实,你手头那台Windows电脑完全能胜任!本文将带你搭建一套完全本地化、免费、可扩展的AI生产力系统,让AI帮你写代码、分析表格、生成文案、处理视频,真正把AI变成你的“赚钱工具”。 系统架构: * 本地大脑:Ollama + DeepSeek模型,负责理解任务、生成内容 * 智能中枢:OpenClaw(原名OpenClaude),负责调用各类工具(Skill) * 赚钱技能:通过安装Skill包,让AI具备特定领域的实操能力 适用人群:

By Ne0inhk

在CodeBuddy中使用自定义AI接口,轻松对接GPT5-Code等大模型,实现AI编程自由!

使用过CodeBuddy的朋友都知道,新用户赠送的大模型使用额度太少了,跑一天就没了,根本就支撑不住我们的Vibe Coding! 比如痴狂哥最近做的项目,没到半天额度就满了:不写一行代码!我用 AI 打造了一款 AI 客户端!(开源) 所以,今天痴狂哥就给大家公布一个自用的方法,让CodeBuddy编辑器能够使用我们自定义的AI接口和大模型,能够无限地愉快自动化编程!实现真正的AI编程自由! 实现效果 1. 让CodeBuddy使用自定义大模型接口,轻松对接如GPT5-Codex等大模型 2. 不消耗用户的使用量,无限免费Vibe Coding! 好了,我们开始吧! 准备工作 1. CodeBuddy海外版(截至至文章发布日期的最新版本0.2.4) 2. Python3(脚本环境) 3. Reqable(抓包工具) 以上软件自行前往官网下载安装。 第一步:重定向大模型接口 将CodeBuddy请求大模型的接口地址,重定向到我们自己的任意大模型API! 安装完毕之后,我们首先打开Reqable,初始化配置,装好证书后启动代理

By Ne0inhk
openJiuwen集成蓝耘AI模型深度解析:从架构设计到企业级Agent实战部署

openJiuwen集成蓝耘AI模型深度解析:从架构设计到企业级Agent实战部署

前言 在人工智能技术从单纯的感知智能向认知智能演进的浪潮中,大语言模型(LLM)的成熟催生了AI Agent(人工智能体)这一全新的应用形态。AI Agent不再局限于传统的单指令执行,而是演进为具备自主感知、推理规划、决策执行能力的智能实体。在这一技术变革背景下,openJiuwen作为一个致力于提供灵活、强大且易用能力的开源Agent平台应运而生。本文将深度剖析openJiuwen的技术架构、核心优势,并基于真实的服务器部署环境,详细拆解从底层环境搭建到上层复杂智能体构建的全过程。 一、 Agentic AI时代的基础设施:openJiuwen概览 openJiuwen的定位不仅是一个开发工具,而是面向生产级应用的Agent全生命周期管理平台。它旨在解决当前大模型应用落地过程中面临的开发门槛高、协同调度难、运行稳定性差等痛点。通过提供标准化的开发框架与高可靠的运行引擎,openJiuwen支持开发者快速构建能够处理各类简单或复杂任务的AI Agent,并实现多Agent间的协同交互。 作为核心代码资产的入口,开发者能在这里查看项目的 Readme 文档、分支管理和最新提交

By Ne0inhk