算法-差分法-c++

分享一下最近学到的算法

先看一道题

输入一个长度为n的整数序列。
接下来输入m个操作,每个操作包含三个整数 L , R , c,表示将序列中[L , R]之间的每个数加上c。
请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数 n 和 m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数 L,R,c,表示一个操作。

输出格式
共一行,包含n个整数,表示最终序列。

Input
6  3
1  2  2  1  2  1
1  3  1
3  5  1
1  6  1

Output
3  4  5  3  4  2

可能第一眼过去直接用for循环遍历就行了,但是那样时间成本高,很难AC,而用差分法能迅速完成。

差分法

 让一个 数字序列某一特定范围内 的所有值都加上或减去一个常数;
所谓差分,就是让序列中的每一个元素与前一个元素作差,将序列中某区间的变化分裂成每个元素变化,省去每次都遍历整个序列的麻烦。

 创建差分数组  b[ i ] = a[ i ] - a[ i - 1 ] ;
即    b[ 1 ] = a[ 1 ] - 0 
        b[ 2 ] = a[ 2 ] - a[ 1 ]
        b[ 3 ] = a[ 3 ] - a[ 2 ]
        b[ 4 ] = a[ 4 ] - a[ 3 ]
        b[ 5 ] = a[ 5 ] - a[ 4 ]
        b[ 6 ] = a[ 6 ] - a[ 5 ]
        ………
        b[ i ] = a[ i ] - a[ i - 1 ]
将式子左右两边相加得出 : a[ i ] = b[ 1 ] +  b[ 2 ] + b[ 3 ] + b[ 4 ] + b[ 5 ] + b[ 6 ] + ……  + b[ i ]
从而可知:原数组是其差分数组的前缀和
若让数列 [ 3 , 5 ] 的元素 +1   即 a[ 3 ] , a[ 4 ] , a[ 5 ] 都 +1


对于  a[ 3 ] = b[ 1 ] +  b[ 2 ] + b[ 3 ];         

// 显然为了让a[ 3 ] + 1,b[ 1 ] , b[ 2 ] , b[ 3 ]其中一个+1就行了,但是,考虑到a[ 2 ] = b[ 1 ] + b [ 2 ],不能改变b[ 1 ] , b[ 2 ],因为会改变a[ 1 ] 和 a[ 2 ] ,只能让 b [ 3 ] + 1


对于  a[ 4 ] = b[ 1 ] +  b[ 2 ] + b[ 3 ] + b[ 4 ]                               

//上述b [ 3 ] + 1 ,从而a[ 4 ] + 1


对于 a[ 5 ] = b[ 1 ] +  b[ 2 ] + b[ 3 ] + b[ 4 ] + b[ 5 ]                 

 //上述b [ 3 ] + 1 ,从而a[ 5 ] + 1


对于  a[ 6 ] = b[ 1 ] +  b[ 2 ] + b[ 3 ] + b[ 4 ] + b[ 5 ] + b[ 6 ]       

//上述b [ 3 ] + 1 ,从而a[ 6 ] + 1,并且 a[ 6 ] 以后的元素都会 + 1 ,这不符合题意,所以让 b[ 6 ] - 1,从而“中和”了由于b[ 3 ]+1导致的 a[ 6 ] 以后的元素都 +1

由上面可知 ,为了让原数组a[L , R] 区间内的每个数 + 1,只需让其差分数组 b[ L ] +1 ,b[ R+1 ] -1 再通过合并差分数组即可得到新的a数组

代码

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int cf[100010];
int main()
{
	//输入一个长度为n数组,接下来一行自行输入数组元素;
	int n, m;
	cin >> n >> m;
	//创建原数组,创建差分数组(最好从下标1开始建,因为cf[1]=a[1]-0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		cf[i] = a[i] - a[i - 1];
	}
	
	//接下来m行,每一行分别输入 l,r,c 表示在数组区间[l,r]的每一个元素都添加常数c;
	while (m--) {						
		int l, r, c;
		cin >> l >> r >> c;		
		cf[l] += c;	; 
		cf[r + 1] -= c; //为什么是r+1 ?
	}
	//合并为新数组
	for (int i = 1; i <= n; i++)
	{
		//--------a[i]就是cf[i]的前缀和--------
		//a[i]=a[i-1]+cf[i] 等价于 a[i]=cf[1]+cf[2]+……cf[i] (如下)
		//a[3]=a[2]+cf[3]
		//     a[2]=a[1]+cf[2]
		//          a[1]=0+cf[1]
		
		cf[i] = cf[i] + cf[i - 1];		
		//每一次输出的cf[i]就可以看成a[i],因为它随着循环添加了cf[1]……cf[i-1]
		cout << cf[i] << " \n"[i == n];	
	}
}

 再来看一道牛客的题目

题目描述:
        牛牛现在在花园养了n棵树,按顺序从第1棵到第n棵排列着。牛牛每天会按照心情给其中某一个区间的树浇水。例如如果某一天浇水的区间为[2,4],就是牛牛在这一天会给第2棵,第3棵和第4棵树浇水。树被浇水后就会成长,为了简化问题,我们假设在初始时所有树的高度为0cm。每过去一天树会自然成长1cm,每次树被浇水后当天会额外成长1cm。m天中牛牛每天都都会选一个区间[ L , R ]对这个区间内的树进行浇水,牛牛想知道m天后有多少棵树的高度为奇数,你能告诉牛牛吗?

示例1
输入 3,2,[1,2],[2,3]
返回值 2

说明
第一天后 第一棵树高度为2,第二棵树高度为2,第三棵树高度为1
第二天后 第一棵树高度为3,第二棵树高度为4,第三棵树高度为3,一共两棵树高度为奇数,所以输出2

 代码

#include<bits/stdc++.h>
using namespace std;
int a[100100];		//树的高度
int b[100100];
int main()
{
	int n, m;
	cin >> n >> m;		//n棵树,m天后
	for (int i = 1; i <= n; i++)
	{
		a[i] = 0;		//每棵树的高度初始化为0;
						//初始值均为0,其差分数组b[i]=a[i]-a[i-1]也为0;故两数组相同,所以无需创建b
	}

	while (m--) {
		int lf, rg;
		cin >> lf >> rg;		//给[lf,rg]之间的树浇水,每棵树高度+1;
		a[lf]++;
		a[rg + 1]--;
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		a[i] += a[i - 1];
		cout << a[i] << " \n"[i == n];	//输出m天后每棵树的高度
		if (a[i] % 2 == 1) ans++;	//有多少颗树高度为奇数
	}
	cout << ans << endl;
}

这两题的区别:一个题的序列有初始值,另一个题的序列初始值均为0;后者其差分数组和原数组相同,所以可以不用“显式的创建”差分数组

第一次写博客,继续加油!
今日偏好歌曲:《美人鱼》——周杰伦

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值