Loading

「学习笔记」前缀和与差分

一维前缀和

就是一个数列前 \(n\) 项的总和
如何实现前缀和?
代码:

const int N=1e5+10;
int sum[N],a[N]; //sum[i]=a[1]+a[2]+a[3].....a[i];
for(int i=1;i<=n;i++)
{ 
    sum[i]=sum[i-1]+a[i];   
}

查询某个数或某一段区间的区间和,查询时间复杂度为 \(O_1\),预处理的时间复杂度为 \(O_n\)
查询代码:

scanf("%d%d",&l,&r);
printf("%d\n", sum[r]-sum[l-1]);

原理:
\(sum_l = a_1 + a_2 + a_3 + a_4 + ... + a_l\)
\(sum_r = a_1 + a_2 + a_3 + a_4 + ... + a_l + a_{l+1} + a_{l+2} + ... + a_r\)
\(l — r\) 区间的和
\(sum_r - sum_{l-1} = a_l + a_{l+1} + ... + a_r\)

二维前缀和

其实跟一维的原理一样,只不过它所记录的是是左上角\((1,1)\)到右下角\((i,j)\)所包含的矩阵元素的和

如图,蓝色部分是我们要求的,我们可以用目前已知的\((1,1,i,j)-(1,1,i,j-1)-(1,1,i-1,j)\)来求,但这里要注意,\((1,1,i-1,j-1)\)在这里被减了2遍,所以要再加上\((1,1,i-1,j-1)\)
所以 \(a(i, j) = s(i, j) - s(i, j-1) - s(i-1, j) + s(i-1, j-1)\)
因而也可以得到二维前缀和的预处理公式:
\(s(i, j) = a(i, j) + s(i, j-1) + s(i-1, j) - s(i-1, j-1)\)
预处理时间复杂度为 \(O_{nm}\) ,查询时间复杂度为 \(O_1\)

一维差分

可以看作前缀和的逆运算
给定原数组:\(b_1,b_2,b_3,b_4......\)
现在构造一个数组:\(a_1,a_2,a_3,a_4,a_5......\)
我们让它们做到 \(a_i=b_1+b_2+b_3+b_4+...+b_i\)
可以看出,\(a\)\(b\) 数组的前缀和数组,那么反过来,\(b\) 就是 \(a\) 的差分数组
每一个 \(a_i\) ,都是 \(b\)\(1\) 开始到 \(i\) 的区间和
构造:
\(b_1 = a_1 - a_0\)
\(b_2 = a_2 - a_1\)
\(b_3 = a_3 - a_2\)
\(......\)
差分数组有什么用?
\(l —— r\) 区间里的元素都加上 \(x\)
按照一般做法,

for(int i=l;i&lt;=r;++i)
{
  a[i]+=x;
}

这种做法是 \(O_n\) 的,数据一大,我们就承受不了这种复杂度了
这里,我们可以考虑差分
上面可以看出,差分记录的只是差,所以 \(b_{l+1} \sim b_r\) 之间不受影响,因为都是加 \(x\) ,所以差不变,但是, \(a_l+x\) ,那么,\(a_l - a_{l-1} = b_l + x\) ,所以 \(b_l\) 要加上 \(x\) (这就是为什么前面我说的是\(l+1 \sim r\) 而不是 \(l \sim r\)),同理,\(a_r + x\) ,那么,\(a_{r+1} - a_r = b_{r+1} - x\)
由此可以看出,我们只要在 b[l]+=x,b[r+1]-=x,就可以解决问题了,时间复杂度为 \(O_1\)(忽略常数)

二维差分

原理差不多,但相对于二维前缀和,二维差分更复杂一些
我们要始终记得,原数组 \(a\) 是差分数组 \(b\) 的前缀和数组,一旦 \(b\) 有变化,那么,\(a\) 也会随之改变
构造:

void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}
for(int i=1;i<=n;i++)
{
    for(int j=1;j<=m;j++)
    {
        insert(i,j,i,j,a[i][j]);    //构建差分数组
    }
}

上面这种构造要把 \(b\) 数组设为空,当然,也有关于二维差分操作也有直接的构造方法,公式如下:
\(b(i, j) = a(i, j) - a(i-1,j) - a(i, j-1) + a(i-1, j-1)\)
\((i,j)\)\((h,k)\)的矩阵的元素都加上 \(X\)
因为 \(a(i,j) + x\) ,所以 \(a(i, j) - a(i-1, j) - a(i, j-1) + a(i-1, j-1) = b(i,j) + x\)
同理
因为 \(a(h, j) + x\) ,所以 \(a(h+1,j) - a(h, j) - a(h+1,j-1) + a(h,j-1) = b(h+1,j) - x\)
因为 \(a(i, k) + x\) ,所以 \(a(i,k+1) - a(i-1,k+1) - a(i,k) + a(i-1,k) = b(i,k+1) - x\)
因为 \(a(h, k) + x\) ,所以 \(a(h+1,k+1) - a(h,k+1) - a(h+1,k) + a(h,k) = b(h+1,k+1) + x\)
\(b(i,j) \sim b(h,k)\) 除去 \(b(i,j)\) 因为都 \(+x\),所以不受影响
求和

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=m;j++)
    {
        b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
    }
}

这种求和方法,\(b\) 数组每一次都在变化,而且每一次变化都标留了(+=),当然,也有其他求和版本

long long c[1000][1000];
long long x,y;
scanf("%lld%lld",&x,&y);
for(int i=1;i<=x;++i)
{
    for(int j=1;j<=y;++j)
    {
        c[x][y]+=b[i][j];
    }
}
posted @ 2022-06-28 11:33  yi_fan0305  阅读(52)  评论(0编辑  收藏  举报