当前位置: 首页 > article >正文

浅说差分算法(上)

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

差分

当我们这里有 n n n个数,现在我要对其中一段进行操作,每次可能加上一个数,也有可能减去一个数,请输出最终的数列。
( 0 < = n < = 1 0 6 0<= n <= 10^6 0<=n<=106)

正常思路

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

#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就行了,这样我们在进行前缀和的时候就不会多加一段了。
因此,我们得到了区间修改的核心代码

//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。
请你输出进行完所有操作后的序列。

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

#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;
}

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

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

#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(XY)
也就是
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(XY)+1

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

#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;
}

http://www.kler.cn/a/324786.html

相关文章:

  • 一个win32 / WTL下多线程库(CThread类)的使用心得
  • 一文详细深入总结服务器选型
  • 详解map与multimap容器
  • nginx源码安装配置ssl域名
  • 快速上手:Docker 安装详细教程(适用于 Windows、macOS、Linux)
  • Hybird和WebView
  • excel-VBA知识点记录
  • 服务器数据恢复—SAN环境下LUN映射出错导致文件系统一致性出错的数据恢复案例
  • 物联网系统中OLED屏主流驱动方案详解
  • 每日OJ题_牛客_HJ108求最小公倍数_C++_Java
  • unixODBC编程(四)插入数据
  • 【js】Node.js的fs的使用方法
  • 长沙某公司.Net高级开发面试题
  • 【C语言零基础入门篇 - 15】:单链表
  • 甄选范文“论应用服务器基础软件”,软考高级论文,系统架构设计师论文
  • 静态路由和默认路由(实验)
  • 海滨体育馆管理系统:SpringBoot实现技巧与案例
  • 活动在线报名小程序源码系统 自主提交表单+创建表单 带完整的安装代码包以及搭建部署教程
  • LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大
  • 小阿轩yx-Ansible部署与应用基础
  • linux semaphore信号量操作
  • 基于nodejs+vue的农产品销售管理系统
  • 如何制作小程序商城
  • NLP任务的详细原理与步骤的详细讲解
  • 算法 求最大葫芦数
  • 如何选择合适的跨境网络专线?