浅说差分算法(上)

今天我们来讲讲对于数列修改的一个小技巧,差分

差分

当我们这里有 n n n个数,现在我要对其中一段进行操作,每次可能加上一个数,也有可能减去一个数,请输出最终的数列。

( 0 < = n < = 1 0 6 0<= n <= 10^6 0<=n<=106)

正常思路

我们正常来看,如果想要对一个区间进行操作,我们只能遍历这个区间,一个一个的去修改,但是这样的的时间复杂度是 O ( n 2 ) O(n^2) O(n2),是过不了的,所以我们要换个思路。(当然如果你只想骗点分也是可以的)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int INF=1e7;
int a[INF]
int main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		cin>>a[i];
	}
	for (int i=1;i<=m;i++){
		int l,r,x;
		cin>>l>>r>>x;
		for (int j=l;j<=r;j++){
			a[j]+=x;
		}
	}
	for (int i=1;i<=n;i++){
		cout<<a[i]<<" ";
	}
	return 0;
}

差分思路

虽然我不知道这个思路是怎么来的,但是不重要,它其实就是一个思想,也就是利用以小带大的思想去操作。

差分就是对相邻的两个数求差值,然后这些差值的前缀和就是这个数,也就是前缀和是差分的逆运算。我们可以来看一个例子:

此时差分数组的前缀和就是原数组,当我们要修改一个区间时,只需要更改差分数组对应区间的首相和最后一项的下一位的值就行了。比如我们想要更改2~4这个区间的值,我们只需要将第三位的-3+1,并且将第五位的-2-1就行了,这样我们在进行前缀和的时候就不会多加一段了。

因此,我们得到了区间修改的核心代码:

cpp 复制代码
//ans表示差分数组
void change(int l,int r,int x){
	ans[l]+=x;
	ans[r+1]-=x;
	return;
}

下面我们将以一道例题,来给大家称述完整的代码

Descirption

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列。

分析题意后发现,这就是最简单的最标准的差分,话不多说,直接上代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int INF=200000+1000;
long long a[INF],b[INF];
int main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i]-a[i-1];//计算差分数组
	}
	for (int i=1;i<=m;i++){
		int l,r,c;
		cin>>l>>r>>c;
		b[l]+=c;//核心代码
		b[r+1]-=c;
	}
	for (int i=1;i<=n;i++){
		b[i]+=b[i-1];//计算前缀和
		cout<<b[i]<<" ";
	}
	return 0;
}

我们还是老样子,上例题。


虽然我很想说,我不能帮帮她,分析题意可知,这也是一道一模一样的差分裸题,我们只需要在最后求解前缀和的时候,找个最小值就行了,一点也不难。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int INF=5*1e6+10;
int a[INF],b[INF];
int minn=INT_MAX;
int main(){
	int n,p;
	cin>>n>>p;
	for (int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i]-a[i-1];
	} 
	for (int i=1;i<=p;i++){
		int x,y,z;
		cin>>x>>y>>z;
		b[x]+=z;
		b[y+1]-=z;
	}
	for (int i=1;i<=n;i++){
		b[i]+=b[i-1];
		minn=min(b[i],minn);
	}
	cout<<minn;
	return 0;
}

这道题很有意思,在洛谷上是道绿题 题目传送门

因为是区间整体加减1,所以我们很自然的就可以想到用差分求解。

这道题可以看做求出原序列的差分之后,将 S[2...n] 全部变为0所需的最少的步数和可以使 S[1] 变为多少种不同的数。

很明显的,在我们求出的差分数组中,有正数也有负数,要消除这些数,使得它们全部归零,我们有以下3种可行的操作:

选取一个正数(X)和一个负数(Y),使正数减1,负数加1,这样经过N次操作,我们一定可以以最少的代价将绝对值较小的一方归零,代价为 a b s ( m i n ( X , Y ) ) abs(min(X,Y)) abs(min(X,Y))

选取一个正数(X),使其与 S[1] 配对,这样,我们经过N次操作,一定可以将它归零,代价为:abs(X)

选取一个负数(Y),使其与 S[n+1] 配对,这样,我们经过N次操作,一定可以将它归零,代价为:abs(Y)

经过上述分析,我们就能够推导出本题的解题公式:
a n s = a b s ( m i n ( X , Y ) ) + a b s ( X − Y ) ans=abs(min(X,Y))+abs(X−Y) ans=abs(min(X,Y))+abs(X−Y)

也就是
a n s = a b s ( m a x ( X , Y ) ) ans=abs(max(X,Y)) ans=abs(max(X,Y))

需要注意的是这里的X,Y是差分数组中所有正数的和与所有负数的和的绝对值

最后我们还要求能构成几组解,这很容易可以推出:
a n s = a b s ( X − Y ) + 1 ans=abs(X−Y)+1 ans=abs(X−Y)+1

那么我们就可以直接写啦!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
long long a[INF];
long long ans1,ans2;
int main(){
	int n;
	cin>>n;
	for (int i=1;i<=n;i++){
		cin>>a[i];
		if (i==1)continue;
		long long c=a[i]-a[i-1];//求解差值
		if (c>0)ans1+=c;//判断是正是负
		else ans2+=abs(c);
	}
	cout<<max(ans1,ans2)<<endl;
	cout<<abs(ans1-ans2)+1;
	return 0;
}
相关推荐
Ajiang282473530430 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海34 分钟前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
‘’林花谢了春红‘’5 小时前
C++ list (链表)容器
c++·链表·list
搬砖的小码农_Sky7 小时前
C语言:数组
c语言·数据结构
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Swift社区8 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman8 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
先鱼鲨生8 小时前
数据结构——栈、队列
数据结构