0x03.2_差分

一维差分:

类似于数学中的求导和积分,差分可以看成前缀和的逆运算。

差分数组:

首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n];

然后我们构造一个数组b : b[1] ,b[2] , b[3],,,,,, b[i];

使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]

也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。

考虑如何构造差分b数组?

最为直接的方法

如下:

a[0 ]= 0;

b[1] = a[1] - a[0];

b[2] = a[2] - a[1];

b[3] =a [3] - a[2];

........

b[n] = a[n] - a[n-1];

图示:

我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组 。

知道了差分数组有什么用呢? 别着急,慢慢往下看。

话说有这么一个问题:

给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c ,,,,,, a[r] + c;

暴力做法是for循环l到r区间,时间复杂度O(n),如果我们需要对原数组执行m次这样的操作,时间复杂度就会变成O(n*m)。有没有更高效的做法吗? 考虑差分做法,(差分数组派上用场了)。

始终要记得,a数组是b数组的前缀和数组,比如对b数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。

首先让差分b数组中的 b[l] + c ,通过前缀和运算,a数组变成 a[l] + c ,a[l+1] + c,,,,,, a[n] + c;

然后我们打个补丁,b[r+1] - c, 通过前缀和运算,a数组变成 a[r+1] - c,a[r+2] - c,,,,,,,a[n] - c;

为啥还要打个补丁?

我们画个图理解一下这个公式的由来:

b[l] + c,效果使得a数组中 a[l]及以后的数都加上了c(红色部分),但我们只要求l到r区间加上c, 因此还需要执行 b[r+1] - c,让a数组中a[r+1]及往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生改变。

因此我们得出一维差分结论:给a数组中的[ l, r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c。时间复杂度为O(1), 大大提高了效率。


总结:

练习一道板子题吧~

(1)问题描述—— IncDec序列

给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

输入格式

第一行输入正整数 n。

接下来 n 行,每行输入一个整数,第 i+1 行的整数代表 aiai。

输出格式

第一行输出最少操作次数。

第二行输出最终能得到多少种结果。

数据范围

0<n≤105
0≤ai<2147483648

输入样例:

4
1
1
2
2

输出样例:

1
2

思路:

求出 a 的差分数列 b, 题目对a的描述,相当于每次可以选出b1, b2, ..., b(n + 1)中的任意两个数,一个加1, 一个减1.目标是把b2, b3,.., bn变为全零。

在b1, b2, ..., bn里面选两个数进行上面的操作
        (1) 2 <= i, j <= n;(正负配对,这样一个加,一个减,能够更快的达到目的——全部变为0)
        (2) i = 1; 2 <= j <= n;
        (3) 2 <= i <= n; j = n + 1;
        (4) i = 1; j = n + 1;

设b2, b3, ..., bn + 1中的正数总和为p, 负数总和为q, 首先执行第一类正负配对的操作,可以执行min(p, q)次,剩余|p - q|个未配对,每个可以选择第二或第三类配对,共需|p - q|次

所以一共需要min(p, q) + |p - q|次。

AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll, ll> PLL;
const int N = 100010;
//int dx[N] = {-1, 0, 1, 0, 0}, dy[N] = {0, 1, 0, -1, 0};

ll n;
ll a[N];//原数列
ll b[N];//差分数列

void insert(ll l, ll r, ll c)
{
	b[l] += c;
	b[r + 1] -= c;
}

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++ ) cin >> a[i];
	
	for(int i = 1; i <= n; i ++ ) insert(i, i, a[i]);//构造差分数列

	/*
		在b1, b2, ..., bn里面选两个数进行上面的操作
		(1) 2 <= i, j <= n;(正负配对,这样一个加,一个减,能够更快的达到目的——全部变为0)
		(2) i = 1; 2 <= j <= n;
		(3) 2 <= i <= n; j = n + 1;
		(4) i = 1; j = n + 1;
	*/
	
	ll sum1 = 0, sum2 = 0;
	for(int i = 2; i <= n; i ++ )
	{
		if(b[i] >= 0) sum1 += b[i];
		else sum2 -= b[i];
	}
	
	
	ll res = min(sum1, sum2);
	ll t = abs(sum1 - sum2);
	
	ll ans = res + t;
	
	cout << ans << "\n" << t + 1 << endl;
	return 0;
}

(2)问题描述——最高的牛:

有 N 头牛站成一行,被编队为 1、2、3…N,每头牛的身高都为整数。

当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。

现在,我们只知道其中最高的牛是第 P 头,它的身高是 H ,剩余牛的身高未知。

但是,我们还知道这群牛之中存在着 M 对关系,每对关系都指明了某两头牛 A 和 B 可以相互看见。

求每头牛的身高的最大可能值是多少。

输入格式

第一行输入整数 N,P,H,M,数据用空格隔开。

接下来 MM 行,每行输出两个整数 A 和 B ,代表牛 A 和牛 B 可以相互看见,数据用空格隔开。

输出格式

一共输出 N 行数据,每行输出一个整数。

第 i 行输出的整数代表第 i 头牛可能的最大身高。

数据范围

1≤N≤5000
1≤H≤1000000
1≤A,B≤10000
0≤M≤10000

输入样例:

9 3 5 5
1 3
5 3
4 3
3 7
9 8

输出样例:

5
4
5
3
4
4
5
5
5

注意:

  • 此题中给出的关系对可能存在重复

AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair<ll, ll> PLL;
const int N = 5010;
//int dx[N] = {-1, 0, 1, 0, 0}, dy[N] = {0, 1, 0, -1, 0};

int n, p, h, m;
int a[N];
int b[N];
bool book[N][N];

void insert(int l, int r, int c)
{
	b[l + 1] += c;
	b[r + 1] -= c;
}

int main()
{
	cin >> n >> p >> h >> m;
	b[1] = h;
	for(int i = 2; i <= n; i ++ ) b[i] = 0;
	while(m --)
	{
		int a, b;
		cin >> a >> b;
		if(a > b)
			swap(a, b);
		if(!book[a][b])
		{
			insert(a, b - 1, -1);
		}
		book[a][b] = true;
	}
	
	a[1] = b[1];
	for(int i = 2; i <= n; i ++ )
	{
		a[i] = b[i] + a[i - 1];
	}
	
	for(int i = 1; i <= n; i ++ )
	{
		cout << a[i] << endl;
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值