The Trip On Abandoned Railway題解 二次差分樹狀數組

題目鏈接傳送門

題目描述

題目大意是,一條廢棄的地鐵路線上有n個火車站,第i個火車站初始盤踞着ai個鬼鬼。
然後有一輛幽靈列車會突然從某個車站出現,並從車上下來x個鬼。即該車站鬼魂數量+x。列車會一路行駛直至終點,每經過一個車站,從列車上下來的鬼魂數量就比前一個車站下來的多d個
即從車上下來x+k*d個鬼魂,其中k爲從列車出現經過的車站數。
另有一個驅魔師,到了某個車站後,就將該車站的所有鬼魂全部超度,即,將該車站鬼魂數置爲0。

操作1:幽靈列車在x站出現,並下來y個鬼魂。(後續每經過一個車站就多d個)
操作2:驅魔師前往x站,輸出該站鬼魂數後,將該站鬼魂數清零。

解題思路

採用二次差分樹狀數組
時間複雜度分析
操作1是區間修改,時間複雜度是o(n),操作2是單點查詢,單點修改,時間複雜度是o(1),有m個操作。故時間複雜度爲o(m*n)。
十組數據,n,m都是105 的數量級,直接計算數組會超時。
區間修改,單點查詢,單點修改,這道題顯然要用樹狀數組或線段樹來做。

樹狀數組或線段樹可以將區間修改和查詢轉爲logn的時間複雜度,這樣總體的時間複雜度就是o(m*logn)
這裏我們選擇代碼量少的樹狀數組。

再往下看時請確保你有樹狀數組的基礎,如果你還不懂樹狀數組如何區間修改和查詢的,這有一篇寫的不錯的博客——傳送門
樹狀數組解決區間修改問題,通常是用差分數組。
一次差分只能解決區間內統一增加k的修改

設車站鬼魂數組爲a[i] 則差分數組d[i]=a[i]-a[i-1]
a[i]=∑0<j≤i d[j] ——①
但是我們發現對於操作1來講,除d[x]+=y外,d[x+1]d[n]都要自增d。——②
又是一個區間修改,但此時的區間修改變成了統一增加d。
這樣,操作1就可以轉化爲對d[]的區間修改(②),對a[i]的查詢可以轉換爲對d[]的區間查詢(①)。而實現這兩種功能,只需再對d[]進行差分就好。
設二重差分數組k[i]=d[i]-d[i-1]
我們只需針對k[i]i*k[i]建樹即可。

做個清晰的對比,來搞清楚操作1和操作2到底做了什麼事情。
操作1:

下標:x x+1 x+2
a[x]+y a[x+1]+y+d a[i+2]+y+2d
d[x]+y d[x+1]+d d[x+2]+d
k[x]+y k[x+1]+d-y k[x+2]

add(x,y);
add(x,d-y);
void add(int i,ll x){
    ll ix=i*x;
    while(i<=n){
        c1[i]=c1[i]+x;
        c2[i]=c2[i]+ix;
        i+=lowbit(i);
    }
}

其中,c1代表k[]的樹狀數組,c2代表i*k[i]的樹狀數組
操作2:
查詢a[i]

a[i]=∑0<j≤i d[j]
= k[1] ,+ k[1] + k[2], + k[1] + k[2] + k[3] + … + k[1] + k[2] + k[3] + … + k[i]
= Σ(i - j + 1) * k[j] (j從1到 i )
= ∑1≤j≤i (i+1)*k[j] - ∑1≤j≤i j * k[j]

ll getSum(int i){
    int k=i;
    ll s1=0,s2=0;
    while(i>0){
        s1=s1+c1[i];
        s2=s2+c2[i];
        i-=lowbit(i);
    }
    return (k+1)*s1-s2;
}

a[x]清零

下標:x x+1 x+2
a[x]-a[x] a[x+1] a[x+2]
d[x]-a[x] d[x+1]+a[x] d[x+2]
k[x]-a[x] k[x]+2*a[x] k[x+2]-a[x]
ll ans=getSum(x);
add(x,-ans);
add(x+1,2*ans);
add(x+2,-ans);

另一個關於取模的問題。
我第一次提交的時候沒有通過全部的樣例。
原因是因爲我在計算差分數組的時候取模了。
後來想了想,差分數組反映的是差值,是斜率,是數據的變化。如果取模則會影響到計算,從而出錯。所以差分數組不取模,最後的結果取模就行。

上代碼

#include <iostream>
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
typedef long long ll;
ll a[maxn];
ll c1[maxn];    //維護k[i]二重差分前綴和的樹狀數組
ll c2[maxn];    //維護i*k[i]二重差分前綴和的樹狀數組
int n,m,d;
int lowbit(int x){
    return x&(-x);
}
void add(int i,ll x){
    ll ix=i*x;
    while(i<=n){
        c1[i]=c1[i]+x;
        c2[i]=c2[i]+ix;
        i+=lowbit(i);
    }
}
ll getSum(int i){
    //a[i]=sum(d[i])=sum(k[i])*(n+1)-sum(i*k[i])
    int k=i;
    ll s1=0,s2=0;
    while(i>0){
        s1=s1+c1[i];
        s2=s2+c2[i];
        i-=lowbit(i);
    }
    return (k+1)*s1-s2;
}
int dd[maxn];
int main(){
    int T;
    cin>>T;
    while(T--){
        cin>>n>>m>>d;
        //數組qingling
		for(int i=0;i<=n;i++)c1[i]=c2[i]=0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            //計算一層差分
            dd[i]=a[i]-a[i-1];
            //計算二層差分
            add(i,dd[i]-dd[i-1]);
            
        }
        int op,x,y;
        while(m--){
            cin>>op;
            if(op==1){
                cin>>x>>y;
                add(x,y);
                add(x+1,d-y);
            }
            else{
                cin>>x;
                ll ans=getSum(x);
                cout<<(ans+mod)%mod<<endl;
                add(x,-ans);
                add(x+1,2*ans);
                add(x+2,-ans);
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章