浅说差分算法(上)

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

差分

当我们这里有 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;
}
相关推荐
The丶Closer17 分钟前
10. 排序
java·数据结构·算法·排序算法
AVICCI27 分钟前
C++ 机器人相关面试点
c++·面试·机器人
奇点 ♡1 小时前
【线程】线程安全的单例模式
linux·c语言·c++·安全·单例模式·visual studio code
liuyang-neu1 小时前
力扣 简单 104.二叉树的最大深度
java·数据结构·算法·leetcode
simple_ssn1 小时前
【C语言刷力扣】2079.给植物浇水
c语言·算法·leetcode
是小恐龙啊1 小时前
单链表的增删改查(数据结构)
c语言·开发语言·数据结构·算法
什码情况1 小时前
报数游戏 - 华为OD统一考试(E卷)
java·python·算法·游戏·华为od·笔试·华为od机试
鸽芷咕1 小时前
【C++报错已解决】std::ios_base::sync_with_stdio
开发语言·c++·ios·bug
去往火星2 小时前
8.12 矢量图层面要素单一符号使用八(随机标记填充)
c++
Gabriel Drop Out2 小时前
17年数据结构考研真题解析
数据结构·考研