ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

浅谈差分

2022-07-26 08:33:28  阅读:185  来源: 互联网

标签:y2 浅谈 int 差分 lca x2 x1


1.前言

前置芝士:

同步于 \(luogublog\) 发布。

  • 基本树上操作,lca。(用于树上差分。)

如有错误,欢迎各位大佬指出。(顺便复习一下远古算法。)

2.什么是差分

我们先给定一个数组 \(a\),长度为 \(n\),我们可以构造一个差分数组 \(b\),使得对于任意的 \(i(1\le i \le n)\),\(\displaystyle\sum_{j = 1}^{i} b_j=a_i\)。

那么如何构建一个普通的差分数组呢?

不难想到,我们假定 \(a_0=0\),则此时,对于任意的 \(b_i\),我们令它等于 \(a_i-a_{i-1}\),则当我们算 \(\displaystyle\sum_{j=1}^{i}b_j\) 时,所有 \(a_1,a_2...a_{i-1}\) 都会抵消掉,只剩下 \(a_i\),也正好满足了我们的前提条件。

3.差分数组的最普通应用

首先,我们先引入一个例题 P2367 语文成绩。题目意思大概就是说先给你一个序列,然后在进行区间加,最后求得区间的最小值即可。

而对于这种区间加减的操作,正是差分能够大展拳脚的地方。

我们先维护一个差分数组 \(b\)(以后皆假设差分数组为 \(b\)。),先将它全部初始化为0。假设当前我们面临的操作是将 \([l,r]\) 这个区间全部加上 \(x\)。由于差分的前缀和便是原数组,所以我们可以一开始将 \(b_l+x\),但是当你在算前缀和的时候,对于 \(r+1\) 及以后的前缀和,他都会多算一个 \(+x\),所以,为了将其抵消掉,我们需要将 \(b_{r+1}-x\)。

最后,处理完这些询问之后,我们在最后求一次前缀和即可。

可以发现,差分修改操作时间复杂度为 \(O(1)\),查询时间复杂度为 \(O(n)\)。

最后贴上一份代码:

#include<bits/stdc++.h>
using namespace std;
int n,q;
int a[5000005],c[5000005];
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	while(q--)
	{
		int l,r,x;
		scanf("%d%d%d",&l,&r,&x);
		c[l]+=x,c[r+1]-=x;//由于是差分,将c[l]+x,但为了抵消掉它对后面的贡献,我们将 c[r+1]-x 
	}
	int minn=INT_MAX;
	for(int i=1;i<=n;++i)
	{
		c[i]+=c[i-1];//求前缀和 
		a[i]+=c[i];//原数组记得加上 
		minn=min(minn,a[i]);//最小值 
	}
	cout<<minn<<endl;
}

4.差分数组的二维形式

我们上述的,全都是一个序列(一维)的情况,但是,差分仍然可以扩展到二维。(对于其定义,与一维相类似,这里不做过多赘述)

假设我们当前修改的二维区间为 \((x1,y1)\) 到 \((x2,y2)\),加上 \(x\)(\(x1\le x2,y1\le y2\))。显然,我们一开始仍然需要将起始位置 \(b_{x1,y1}+x\)。但随即我们可以发现,对于所有 \(x1-n,y1-n\) 它的值都被加上了 \(x\),但这个区间实际只能管到 \((x2,y2)\),所以,对于 \((x1,y2+1)\) 以及 \((x2+1,y1)\) 以后的所有格子都是当前加不到的,所以我们又将 \(b_{x1,y2+1}-x,b_{x2+1,y1}-x\)。但随即我们又可以发现,虽然减是减了,但对于 \((x2+1,y2+1)\) 及以后的值,在差分算前缀和时被减了两次,所以我们需要将 \(b_{x1+1,y2+1}+x\)。

下面配上一个图方便理解。(图略丑,勿喷。)

在这里插入图片描述

假设我们要区间加 \((2,2)-(4,4)\),其中黄色表示被 \(c_{x1,x2}\) 影响到的范围,蓝色表示 \(c_{x1,x2}\) 加后多影响到的地方,及 \(b_{x1,y2+1}-x,b_{x2+1,y1}-x\) 影响到的地方,绿色表示被黄色蓝色一起影响,最终被多减了一次,需要加 \(x\) 的区域。

大概就是:

void chafen(int x1,int y1,int x2,int y2,int x)
{
	b[x1][y1]+=x;
	b[x1][y2+1]-=x;
	b[x2+1][y1]-=x;
	b[x1+1][y2+1]+=x;
}

最后还是给一个比较简单的例题吧。P3717 [AHOI2017初中组]cover,虽然可以不用二维差分做,但当一个练习的板子题还是挺好的。

5.树上差分

这是一种非常常考也非常实用的一种差分形式。

我们先给出一种最基本的树上差分形式。即我们现在要完成的是将 \(x-y\) 的路径上的所有节点 \(+x\)。

然后我们给出树上差分的定义,即我们假设第 \(i\) 个点的点权为 \(a_i\),然后我们维护差分数组 \(b\) 表示对于任意节点 \(i\),使得 \(i\) 的所有子节点的 \(b\) 之和(包括他本身)为 \(a_i\)。

然后,我们来处理树上差分。首先,我们可以把 \(x-y\) 的路径看做 \(x-lca(x,y)-y\) 的路径。(\(lca\) 的求法这里不做赘述。)然后,由于我们要将这一段的路径全部加 \(z\)。我们可以发现,当我们对 \(b_x,b_y\) 分别加上 \(z\),就可以满足将 \(x-\) 根节点的路径以及 \(y-\) 根节点的路径全部 \(+z\),但我们发现,不仅 \(fa_{lca(x,y)}-\) 根节点的路径多加了两遍 \(x\),而且 \(lca\) 这个节点被加了两次,但他只能被加一次,所以它也多加了一次。为了将这些效果抵消,我们可以在 \(b_{lca(x,y)}-z\),则对于 \(lca(x,y)-\) 根节点的路径,我们都被抵消了一次 \(z\)。但是,在抵消之后,\(fa_{lca(x,y}-\) 根节点的路径我们仍然多加了一次 \(z\),所以此时,我们需要将 \(b_{fa_{lca(x,y)}}-z\),便可以完美抵消掉了!

核心代码:

void tree(int x,int y,int z)
{
	b[x]+=z,b[y]+=z;
	b[lca(x,y)]-=z;b[fa[lca(x,y)]]-=z;
}

6.后记

虽然在维护序列时,我们完全可以用线段树树状数组等一列数据结构来代替差分这种查询较慢的结构,但差分终究还是一个好写好想的算法,是不容易出错的。毕竟,如果考场上你在面临树上路径的操作时,不会树上差分,打一个码量极大还容易错的的树链剖分就太吃亏了。

标签:y2,浅谈,int,差分,lca,x2,x1
来源: https://www.cnblogs.com/asaltyfish/p/16519489.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有