分享一下最近学到的算法
先看一道题
输入一个长度为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;后者其差分数组和原数组相同,所以可以不用“显式的创建”差分数组。
第一次写博客,继续加油!
今日偏好歌曲:《美人鱼》——周杰伦