本篇仅讨论一维前缀和。
给定n个数,完成下列操作:
[L, R]
的和;[L, R]
内的所有数增加z,接下来给定x和y,有q个询问,对于每次询问求出[x, y]
的区间和前缀和是一种预处理,可以降低时间复杂度,可以在后面的计算中可以直接应用前面已经算出的结果。
对于一个给定的数列a,其前缀和数列s可由递推求得:
s [ i ] = ∑ j = 1 i a [ j ] s[i]=\sum_{j=1}^i a[j] s[i]=j=1∑ia[j]
int a[maxn], s[maxn];
for(int i=1; i<=n; i++)
{
cin>>a[i];
s[i] = s[i-1]+a[i];
}
部分和是数列a中某个下标区间内的数的和,可以表示为前缀和相减的形式:
s u m [ L , R ] = ∑ i = L R a [ i ] = s [ R ] − s [ L − 1 ] sum[L, R] = \sum_{i=L}^Ra[i] = s[R]-s[L-1] sum[L,R]=i=L∑Ra[i]=s[R]−s[L−1]
所以,针对[1],当询问区间[L, R]
的和时,只需计算s[R]-s[L-1]
即可。
针对[2],直接使用前缀和无法快速满足操作,因此引入差分数组。
差分数组可以在O(1)时间内进行区间[L, R]
的整体加减某一个数。
前缀和运算与差分运算为互逆运算,任意一个数列a的前缀和数组的差分数组是它本身:
d [ i ] = { a [ i ] , if i = 0 a [ i ] − a [ i − 1 ] , if i ≥ 1 d[i]= \begin{cases} a[i], & \text {if $i$ = 0} \\ a[i]-a[i-1], & \text{if $i$ ≥ 1} \end{cases} d[i]={a[i],a[i]−a[i−1],if i = 0if i ≥ 1
换句话说,想要还原成原来的数组,只需要将差分数组做一遍前缀和即可。
int d[maxn];
for(int i=1; i<=n; i++)
d[i] = a[i]-a[i-1];
接下来完成[2]:
我们已经有差分数组d,我们再定义数组d的前缀和数组f,数组f的前缀和数组sum。
d [ i ] = { a [ i ] , if i = 0 a [ i ] − a [ i − 1 ] , if i ≥ 1 d[i]= \begin{cases} a[i], & \text {if $i$ = 0} \\ a[i]-a[i-1], & \text{if $i$ ≥ 1} \end{cases} d[i]={a[i],a[i]−a[i−1],if i = 0if i ≥ 1
f [ i ] = { d [ i ] , if i = 0 f [ i − 1 ] + d [ i ] , if i ≥ 1 f[i]= \begin{cases} d[i], & \text {if $i$ = 0} \\ f[i-1]+d[i], & \text{if $i$ ≥ 1} \end{cases} f[i]={d[i],f[i−1]+d[i],if i = 0if i ≥ 1
s u m [ i ] = { f [ i ] , if i = 0 s u m [ i − 1 ] + f [ i ] , if i ≥ 1 sum[i]= \begin{cases} f[i], & \text {if $i$ = 0} \\ sum[i-1]+f[i], & \text{if $i$ ≥ 1} \end{cases} sum[i]={f[i],sum[i−1]+f[i],if i = 0if i ≥ 1
由上述公式容易得到:
s u m [ y ] − s u m [ x − 1 ] = ∑ i = L R a [ i ] sum[y]-sum[x-1] = \sum_{i=L}^Ra[i] sum[y]−sum[x−1]=i=L∑Ra[i]
举个简单例子:
d[1] = a[1] | f[1] = d[1] = a[1] | sum[1] = f[1] = d[1] = a[1] |
---|---|---|
d[2] = a[2]-a[1] | f[2] = f[1]+d[2] = a[1]+a[2]-a[1] = a[2] | sum[2] = sum[1]+f[2] = a[1]+a[2] |
d[3] = a[3]-a[2] | f[3] = f[2]+d[3] = a[2]+a[3]-a[2] = a[3] | sum[3] = sum[2]+f[3] = a[1]+a[2]+a[3] |
d[4] = a[4]-a[3] | f[4] = f[3]+d[4] = a[3]+a[4]-a[3] = a[4] | sum[4] = sum[3]+f[4] = a[1]+a[2]+a[3]+a[4] |
由此我们也可以看出,任意一个数组a的前缀和数组d的差分数组f是它本身。
那么对于每一个操作m,只需使d[x]加z,d[y+1]减z即可。
继续上面的例子:
d[1] = a[1]+z | f[1] = d[1] = a[1]+z | sum[1] = f[1] = a[1]+1*z |
---|---|---|
d[2] = a[2]-a[1] | f[2] = f[1]+d[2] = a[1]+z+a[2]-a[1] = a[2]+z | sum[2] = sum[1]+f[2] = a[1]+a[2]+2*z |
d[3] = a[3]-a[2] | f[3] = f[2]+d[3] = a[2]+z+a[3]-a[2] = a[3]+z | sum[3] = sum[2]+f[3] = a[1]+a[2]+a[3]+3*z |
d[4] = a[4]-a[3] | f[4] = f[3]+d[4] = a[3]+z+a[4]-a[3] = a[4]+z | sum[4] = sum[3]+f[4] = a[1]+a[2]+a[3]+a[4]+4*z |
以此类推即可。
int n, m, q;
int a[maxn], d[maxn], f[maxn], sum[maxn];
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>a[i];
d[i] = a[i]-a[i-1];
}
cin>>m;
for(int i=1; i<=m; i++)
{
int L, R, z;
cin>>L>>R>>z;
d[L] += z;
d[R+1] -= z;
}
for(int i=1; i<=n; i++)
{
f[i] = f[i-1]+d[i];
sum[i] = sum[i-1]+f[i];
}
cin>>q;
for(int i=1; i<=q; i++)
{
int x, y;
cin>>x>>y;
cout<<sum[y]-sum[x-1]<<endl;
}
51Nod - 1081 子段求和
//#pragma GCC optimize(2)
#include
using namespace std;
int n, q, a[50050];
long long s[50050];
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>a[i];
s[i] = s[i-1]+a[i];
}
cin>>q;
while(q--)
{
int i, l;
cin>>i>>l;
cout<<s[i+l-1]-s[i-1]<<endl;
}
return 0;
}
HDU - 1556 Color the ball
#include
#include
#include
#include
#include
#include
using namespace std;
int n, a, b;
int bl[100010];
int main()
{
//ios::sync_with_stdio(false);
while(cin>>n)
{
if(!n) return 0;
fill(bl, bl+100010, 0);
//while(n--)
for(int i=1; i<=n; i++)
{
cin>>a>>b;
bl[a]++;
bl[b+1]--;
}
for(int i=1; i<=n; i++)
{
bl[i] += bl[i-1];
if(i==n) cout<<bl[i]<<endl;
else cout<<bl[i]<<" ";
}
}
}