題解報告(差分模板)
差分演算法:
給定n長度的陣列,有m個請求,每個請求都是在 [l,r](l<=n;r<=n)區間內加上數c,最後輸出該陣列。
若用暴力法求解,需要迴圈m個請求,每個請求都是需要遍歷陣列 (r-l) 遍 ((r-l)<=n)。其中的時間複雜度為O(mn)(資料範圍在10e3左右)。而運用差分演算法,可以將時間複雜度降到線性即O(m+n);(資料範圍可以在10e7左右)
接下來具體講解差分演算法
由此可以看出差分實際上是字首和運算的逆運算。當需要做出在 [l,r] 區間內的元素加上c的操作時,需要做出的操作為 b[ l ]+=c,由於a陣列為b陣列的字首和,所以a陣列在 l 後的元素都會自動加上c。而由於是在閉區間內,大於 r 下標
的元素需要減去相應的c,來抵消前面相加的c。此時的操作為 b[ r+1] -=c; 而這一步操作的時間複雜度為O(m);
在計算好差分陣列後,(在初始化b陣列時,值為0) 如何得到操作後的陣列a,可以有兩個思路:
個人:對於b陣列計算b陣列中的每一個字首和,即遍歷b陣列,b[i]+=b[i-1];這樣子相當於得到了對於每一個下標的操作c的具體值,a陣列想要得到操作後的陣列,只需要和計算字首和後的b陣列對於的下標相加即可
y總:可以將a陣列中的元素可以看成 [l,l] 區間內加入元素 a [ l ] 到b陣列中去,先做一遍初始化。然後再做m個請求操作。最後計算b陣列的每一個字首和即可。
不管哪種方法,實現的時間複雜度為O(n),即陣列的長度,最終的時間複雜度為O(n+m),為線性
注:當 l 和 r 的數值過大,並且差分後的大多數元素並沒有遍歷到的時候,需要用到離散化的方法。
題目:
Acwing 797.差分(模板題)
//個人方法 #include<iostream> #include<cstring> using namespace std; const int MAXN=1e6; int a[MAXN]; int b[MAXN]; int main() { int n,m; cin>>n>>m; memset(b,0,sizeof(b));//初始化差分陣列 for(int i=1;i<=n;i++) cin>>a[i];//讀入原陣列 for(int i=0;i<m;i++) { int ai,bi,c; cin>>ai>>bi>>c; b[ai]+=c; b[bi+1]-=c; } for(int i=1;i<=n;i++) b[i]+=b[i-1];//計算差分陣列的字首和,使得差分陣列中的對應下標的值為相應的操作後的總值 for(int i=1;i<=n;i++) a[i]+=b[i];//對於a陣列的每一個下標讀入相應的操作 for(int i=1;i<=n;i++) cout<<a[i]<<" "; return 0; }
//y總 #include<iostream> #include<cstring> using namespace std; const int MAXN=1e6; int a[MAXN]; int b[MAXN]; void insert(int l,int r,int c) { b[l]+=c; b[r+1]-=c; return ; } int main() { int n,m; cin>>n>>m; memset(b,0,sizeof(b));//初始化差分陣列 for(int i=1;i<=n;i++) { cin>>a[i];//讀入原陣列 insert(i,i,a[i]);//將原陣列視為在區間[i,i]中加上元素a[i] } for(int i=0;i<m;i++) { int ai,bi,c; cin>>ai>>bi>>c; insert(ai,bi,c); } for(int i=1;i<=n;i++) b[i]+=b[i-1];//計算差分陣列的字首和,使得差分陣列中的對應下標的值為相應的操作後的總值
for(int i=1;i<=n;i++) cout<<b[i]<<" "; return 0; }
Acwing 2041.乾草堆
該題其實也是模板題,只不過題中的原陣列的全部元素預設為0,只需要得到差分陣列的各個字首和即可,由於需要從小到大排序,並且輸出中間值,在操作後還需要排序。並且輸出最中間的值即下標為(n/2+1)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int MAXN=1e6+5; int st[MAXN],d[MAXN]; int main() { int n,k; cin>>n>>k; //memset(st,0,sizeof(st)); memset(d,0,sizeof(d)); for(int i=1;i<=k;i++) { int l,r; cin>>l>>r; d[l]++; d[r+1]--;//對於區間新增乾草堆 } for(int i=1;i<=n;i++) d[i]+=d[i-1];//計算字首和 sort(d+1,d+n+1); cout<<d[n/2+1];//計算中間數 }