題目鏈接傳送門
題目描述
題目大意是,一條廢棄的地鐵路線上有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≤id[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);
}
}
}
}