前綴和,二維前綴和與差分小總結

在瞭解二維前綴和以前,咱們首先須要瞭解一下什麼是前綴和,一維前綴和。c++

若是我給你一串長度爲\(n\)的數列\(a1,a2,a3......an\),再給出\(m\)個詢問,每次詢問給出\(L,R\)兩個數,要求給出區間\([L,R]\)裏的數的和,你會怎麼作,如果沒有了解過前綴和的人看到這道題的想法多是對於\(m\)次詢問,我每次都遍歷一遍它給的區間,計算出答案,這樣子的方法當然沒錯,可是其時間複雜度達到了\(O(n*m)\),若是數據量稍微大一點就有可能超時,而咱們若是使用前綴和的方法來作的話就可以將時間複雜度降到\(O(n+m)\),大大節省了運算時間。至於怎麼用,請看下面一小段代碼數組

a[0]=0;

for(int i=1;i<=n;i++)a[i]+=a[i-1];

沒錯,前綴和顧名思義就是前面\(i\)個數的總和。數組a在通過這樣的操做以後,對於每次的詢問,咱們只須要計算\(a[R]-a[L-1]\)就能獲得咱們想要的答案了,是否是很簡單呢。spa

在知道了最簡單的前綴和以後,咱們再來了解一下什麼是差分。.net

給你一串長度爲\(n\)的數列\(a1,a2,a3......an,\)要求對\(a[L]~a[R]\)進行\(m\)次操做:code

操做一:將\(a[L]~a[R]\)內的元素都加上\(P\)blog

操做二:將\(a[L]~a[R]\)內的元素都減去\(P\)ci

最後再給出一個詢問求\(a[L]-a[R]\)內的元素之和?get

你會怎麼作呢?你可能會想,我對於\(m\)次操做每次都遍歷一遍\(a[L]~a[R]\),給區間裏的數都加上\(P\)或減去\(P\),最後再求一次前綴和就好了。沒錯,這樣子確實也能得出正確答案,但時間複雜度卻高達\(O(M*n)\),對於\(1<=n,m<=1e5\)這個數據範圍來講直接就\(TLE\)了,因此說這個方法不可行。既然這樣不行的話,那咱們要怎麼作才能快速的獲得正確答案呢?是的,這個時候咱們的差分就該派上用場了,咱們新開一個數組\(b\),儲存每一次的修改操做,最後求前綴和的時候統計一下就能快速的獲得正確答案了,詳細請看下面代碼。
簡簡單單it

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+9;
int a[maxn],b[maxn];
int main(){
    int i,j,k,n,m,p;
    cin>>n>>m;
    for(i=1;i<=n;i++){
        cin>>a[i];
    }
    for(i=1;i<=m;i++){
        int L,R,t;
        cin>>t>>L>>R>>p;
        if(t==1){
            b[L]+=p;b[R+1]-=p; //仔細想一想爲何b[R+1]要減去p 
        }
        else{
            b[L]-=p;b[R+1]+=p;
        }
    }
    int add=0;
    for(i=1;i<=n;i++){
        add+=b[i];
        a[i]+=a[i-1]+add;
    }
    int x,y;
    cin>>x>>y;
    cout<<a[y]-a[x-1]<<endl;
}

相信看到這裏,你們已經仔細思考過代碼了,爲何操做一時\(b[R+1]\)要減去\(p\),很簡單,由於操做一我只需對\([L,R]\)區間裏的數加\(p\)\([R+1,n]\)這個區間裏的數不必加\(p\),因此須要減掉\(p\)class

差分講解完畢,接下來咱們終於要開始今天的正題——二維前綴和了。

仍是以小問題的形式來說解二維前綴和吧

給定一個\(n*m\)大小的矩陣\(a\),有\(q\)次詢問,每次詢問給定\(x1,y1,x2,y2\)四個數,求以\((x1,y1)\)爲左上角座標和\((x2,y2)\)爲右下角座標的子矩陣的全部元素和。注意仍然包含左上角和右下角的元素。

怎麼作呢?爲了方便大家理解,上個圖吧。


如圖所示,按題目要求,咱們每次要求的答案就是紅色圓圈所在的區域的值(注意,這裏的\(x1,x2\)表示行,\(y1,y2\)表示列),對比上面這張圖咱們可以發現紅色區域的值等於四個區域的值減去(白色區域+黑色區域),再減去(白色區域+藍色區域),最後由於白色區域被減了兩次,咱們須要再加回來。因此\(ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]\);(注意,此時的\(a\)數組表明的是前綴和)。忽然想起來還沒說怎麼求二維前綴和,很簡單,看下面代碼。

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

爲方便理解貼個圖

假如我想\(求a[2][4]\)的前綴和,我得先加上\(a[1][4]\)的前綴和,再加上\(a[2][3]\)的前綴和,而後這個時候咱們發現實際上\(a[1][3]\)這個部分咱們加了兩遍,因此咱們須要再減去一遍\(a[1][3]\),因而得出公式\(a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1]\)

接下來看完整代碼吧。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+9;
int a[maxn][maxn];
int main(){
    int i,j,k,n,m,q;
    cin>>n>>m>>q;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++)
        cin>>a[i][j];
    }
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++)
        a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
    }
    for(i=1;i<=q;i++){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        int ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
        cout<<ans<<endl;
    }
}

是否是感受還挺簡單
在學完二維前綴和以後,一些同窗可能會有疑問,一維前綴和能用上差分,那麼二維前綴和能不能用上差分呢?答案是確定的。

那麼怎麼差分呢?方法是和一維相似的,咱們也是須要另開一個數組記錄修改操做,最後求前綴和時統計修改操做,只是二維每一次操做須要記錄4個位置,一維只須要記錄2個位置。具體怎麼作,看下面代碼吧。

for(int i=0;i<m;i++){//m是修改操做次數 
    int x1,y1,x2,y2,p;
    cin>>x1>>y1>>x2>>y2>>p;
    b[x1][y1]+=p;b[x2+1][y2+1]+=p;
    b[x2+1][y1]-=p;b[x1][y2+1]-=p;
}
相關文章
相關標籤/搜索