一维差值维护是一种简单的小算法,该算法用一个巧妙地数列机制解决了多次对数列进行数据加减操作的复杂度,这个算法的思维偏向于动态规范。下面我们从一个问题开始入手介绍这个算法:
问题描述:
已知n个数的数列a,有m次操作,每次操作给定l,r,k三个数,使得al到ar内所有数加上k。注意l到r的区间包含al和ar两个数。
输入数据:
第一行n和m,接下来一行有n个数,表示数列a,接下来有m行,每行有三个数l,r,k,详细解释参考问题描述。
输出数据:
1行n个数,表示经过m次操作之后的数组a。
输入样例:
5 2
1 2 3 4 5
2 4 3
3 5 2
输出样例:
1 5 8 9 7
数据范围:
0<N,M≤100000
ai是int范围内的任意整数,输入数据保证ai+k在int的范围内
对于这个问题而言,我们一开始的思路很简单,就是暴力模拟,每次操作遍历,数组的l到r区间,每个都加上k最后直接遍历数组输出。这样的算法复杂度是O(mn)的,对于这个数据范围肯定会超时。
所以我们是否可以发现一种方式,用这种方式的条件下我们对于某个区间的数据,只需要做两个操作就可以把一个区间都加上某个数。试着去推导这个东西
,我们会去想,什么东西可以在我们做区间加上某一个数这个操作的时候保持不变呢?
于是我们想到了对于数列中的某两个数,这两个数之间的差是不变的。而且当这两个数同时增加k的时候,两数的差仍然不变。因此我们只要求出对于一个数组的所有两个数之间的差,我是否就可以直接在这个差值里面做某个操作,让我的整个数组发生变化。
这就是差值维护,差值维护就是对某一个数列每两两数之间求出差值,这个差值组成一个新数列,设这个数列为S,我对Si加上一个k,则表示ai往后的所有数都加上了一个k,试想一下这个过程。对于一个差值数列,修改某一个位置i的差值,增加一个k,在我把差值数组重新生成原数组时,由于i位置的差值加了k,所以生成的原值也就加了k,而之后的所有数的生成依赖前面的数字,这就使得整个数列从i开始到结束都增加了k。
这里说明一下差值维护的过程,设差值数组为S,原数组为a,则满足S1=a1且Si=ai−ai−1,这样就把S数组维护出来了,之后如果我们想要取得原数组,只需要按照a1=S1,ai=Si+ai−1这个公式即可。那么我们再考虑,对于S数组,我们考虑Si+=k表示的是什么意思。当我执行这个操作的时候,我再生成原数组,会发现ai的值实际上相比操作之前ai的值多了k,而我们是递推得到原数组的,也就是说ai+1的计算需要ai为基础,因为ai增大了k,所以ai+1也同样的增大了k。所以我们做Si+=k这个操作的目的实际上就是把从i到数列结束的所有数都加上了一个k。思考一下这个过程,你会体会到它的巧妙。
所以我们现在来考虑最后一个问题,如何使得从l开始到r的所有数都加上k。根据之前的结论,这个要求就显得很简单了,我们不妨让sl加上k,这表示从l开始到结束的所有数加上k,再让sr+1减去k,表示从r+1到结束的所有数都减去k,由于之前加k现在减k,所以实际上r+1到结束的所有数都没有变化,而l到r的所有数只加了k,满足我们的要求,因此这个算法成立。
根据这个算法就可以效率更高的计算出刚刚问题的解,这个算法的时间复杂度为O(m+n)。
以上就是关于一维差值维护算法的介绍。
include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <cmath> #include <ctime> #include <algorithm> using namespace std; const int MAXN=100010; int n,m,a[MAXN],s[MAXN]; //读入优化 int read(){ char ch=getchar(); bool fl=0; int r=0; if(ch=='-'){ fl=1; ch=getchar(); } while(ch>='0'&&ch<='9'){ r*=10; r+=ch-'0'; ch=getchar(); } return fl?-r:r; } //输出优化 void write(int x){ if(x<0){ putchar('-'); x=-x; } if(x>9){ write(x/10); } putchar(x%10+'0'); } int main(){ freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); n=read(); m=read(); a[0]=read(); s[0]=a[0]; for(int i=1;i<n;++i){ a[i]=read(); s[i]=a[i]-a[i-1];//O(n)的生成差值数组 } for(int i=0;i<m;++i){ int l,r,k; l=read(); r=read(); k=read(); --l; s[l]+=k;//O(2)的操作,所有操作一共O(m) s[r]-=k; } write(s[0]); putchar(' '); for(int i=1;i<n;++i){ s[i]+=s[i-1]; write(s[i]); putchar(' ');//输出接着O(n) } //总复杂度O(m+n) return 0; }