前缀和
给定一个序列,长度为
一个一个加,复杂度
有没有办法加速!
设数组是
其中,
这个数列很好递推求出来
当我们求
这样,预处理和单次查询复杂度是
如图,
该算法不支持在询问途中对原数列进行任何修改!
前缀和算法适用于操作存在逆操作的情况,比如加法的逆操作是减法,所以可以求区间的和。
或者比如乘法的逆操作是除法,所以也可以求区间乘积。
但是有的操作不可逆,比如取最小值,即使我们知道了
差分
前缀和的逆操作
设有数组
其中,
这样,
也就是说,差分是前缀和的逆操作
形式化地表示,我们设
我现在要将
如果用差分,我们可以在
0 0 0 k 0 0 0 -k 0 0 0
然后再前缀和回去,可以发现,在
在
0 0 0 k k k k 0 0 0
这样我们就实现了
如果是多次询问,那每次询问只要
预处理和单次查询的时间复杂度是
该算法不支持在修改途中对原数列进行任何查询,只能所有修改都完成后再查询。
高维前缀和
如果我的数组不是一维的,阁下又将如何应对?
比如是二维的,那么我们有二维原数组
那么对应的,有前缀和数组
也可以通过递推得到:
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
即 黑色= 紫色 + 绿色 - 蓝色 + 右下角(
如果我们要求左上角是
ans=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
即 红色 = 紫色 - 蓝色 - 绿色 + 棕色。
红色面积等于紫色减去蓝色减去绿色减去棕色。
这样对二维来说还可以,但是维度再增高之后就会出现弊端:
这个前缀和数组的算法,其实我们是在对多个维度进行容斥原理:
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
把只有一个维度降低的部分加上,把两个维度都降低的部分减去。
如果升到
采用另一种方法,一维一维的进行前缀和:
for(int j=1;j<=m;++j)
for(int i=1;i<=n;++i) s[i][j]=s[i-1][j]+a[i][j];
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) s[i][j]=s[i][j-1]+s[i][j];
2
3
4
第一个求和是把第一维做前缀和,即
第二次求和再把第二维做前缀和,即
这样平均每个位置花费的时间就是
高维前缀和常用的一个方向是统计一个二进制数字的子集和:
如果
设
比如
对于一个二进制数,假设它有
先枚举维度,然后把这一维度是
for(int i=0;i<k;++i)
{
for(int s=0;s<(1<<k);++s)
{
if((s>>k)&1) f[s]+=f[s^(1<<k)];
}
}
2
3
4
5
6
7
高阶差分
差分可以让
有没有让
有的,我们先求出
即
for(int i=n;i>=1;--i) b[i]=a[i]-a[i-1];
for(int i=n;i>=1;--i) c[i]=b[i]-b[i-1];
2
然后给
0 0 0 1 0 0 0 0 -1 0 0 0
此时将
0 0 0 1 1 1 1 1 0 0 0 0
此时给 b[r+1]
位置再减去一个
0 0 0 1 1 1 1 1 -5 0 0 0
再将
那么
0 0 0 1 2 3 4 5 0 0 0 0
同理还可以用三次差分做到区间加 1 3 6 10 15
的效果。
其实一阶差分可以给区间加零次多项式(常数)
二阶差分可以给区间加一次多项式(
三阶差分可以给区间加二次多项式 (
综上,