洛谷 - P3246 [HNOI2016]序列(莫队+单调栈)
題目鏈接:點擊查看
題目大意:給出一個長度為 nnn 的序列,再給出 mmm 次詢問,每次詢問需要回答一個區間 [l,r][l,r][l,r] 內所有子區間的最小值之和
題目分析:因為可以離線,所以考慮莫隊,這個題的難點是如何處理 [L,R][L,R][L,R] 遞推到 [L,R+1][L,R+1][L,R+1]
從 [L,R][L,R][L,R] 推到 [L,R+1][L,R+1][L,R+1],實質上多了 R?L+2R-L+2R?L+2 個子區間,分別是 [L,R+1],[L+1,R+1],...,[R+1,R+1][L,R+1],[L+1,R+1],...,[R+1,R+1][L,R+1],[L+1,R+1],...,[R+1,R+1],我們只需要將這些子區間的最小值之和加上就好了
所以不難想到需要維護一個輔助數組 f[r]f[r]f[r],代表前綴 [1:r][1:r][1:r] 的所有后綴 [1:r],[2:r],...,[r:r][1:r],[2:r],...,[r:r][1:r],[2:r],...,[r:r] 的最小值之和,轉移的話可以利用單調棧維護出 l[r]l[r]l[r] 代表 rrr 左側首個小于 a[r]a[r]a[r] 的位置,那么有 f[r]=f[l[r]]+(r?l[r])?a[r]f[r]=f[l[r]]+(r-l[r])*a[r]f[r]=f[l[r]]+(r?l[r])?a[r],具體意義就是 a[r]a[r]a[r] 充當了左端點位于 (l[r],r](l[r],r](l[r],r] 的這些后綴的最小值
現在我們需要借助 fff 數組快速求出以 R+1R+1R+1 為右端點,左端點位于區間 [L,R+1][L,R+1][L,R+1] ,右端點為 R+1R+1R+1 的子區間的最小值之和,下文稱最小值之和為貢獻
設區間 [L,R+1][L,R+1][L,R+1] 的最小值的位置為 ppp,不難看出左端點位于 [L,p][L,p][L,p] 的子區間的貢獻為 a[p]a[p]a[p],現在我們的問題變成如何計算左端點位于 (p,R+1](p,R+1](p,R+1] 的子區間的貢獻
用我們剛才的 fff 公式展開看看?
設 x=R+1x=R+1x=R+1 :f[x]=f[l[x]]+(x?l[x])?a[x]f[x]=f[l[x]]+(x-l[x])*a[x]f[x]=f[l[x]]+(x?l[x])?a[x]
設 pre=l[x]pre=l[x]pre=l[x] : f[pre]=f[l[pre]]+(pre?l[pre])?a[pre]f[pre]=f[l[pre]]+(pre-l[pre])*a[pre]f[pre]=f[l[pre]]+(pre?l[pre])?a[pre]
設 ppre=l[pre]ppre=l[pre]ppre=l[pre] : f[ppre]=f[l[ppre]]+(ppre?l[ppre])?a[ppre]f[ppre]=f[l[ppre]]+(ppre-l[ppre])*a[ppre]f[ppre]=f[l[ppre]]+(ppre?l[ppre])?a[ppre]
…
不難發現我們會沿著一個 a[pre]a[pre]a[pre] 遞減的序列一直迭代,即 a[ppre]<a[pre]<a[x]a[ppre]<a[pre]<a[x]a[ppre]<a[pre]<a[x],并且直到 ppp 停止,因為 a[p]a[p]a[p] 是區間 [L,R+1][L,R+1][L,R+1] 中的最小值呀,a[p]a[p]a[p] 前面不會再有比它小的數字了
如果將上面的公式自底向上套進去的話,我們會驚奇的發現,f[R+1]?f[p]f[R+1]-f[p]f[R+1]?f[p] 即為所求,也就是左端點位于 (p,R+1](p,R+1](p,R+1] ,右端點為 R+1R+1R+1 的這些子區間的貢獻
到此為止我們可以利用 RMQRMQRMQ 預處理區間最小值的位置然后 O(1)O(1)O(1) 進行區間 [L,R][L,R][L,R] 到 [L,R+1][L,R+1][L,R+1] 的擴展了,對于剩下的 [L,R?1],[L+1,R],[L?1,R][L,R-1],[L+1,R],[L-1,R][L,R?1],[L+1,R],[L?1,R] ,分析起來同理,這里不做過多贅述
有一個小問題就是,這個題的莫隊的修改順序需要先加后減,不然會又WA又RE的
那么莫隊的時間復雜度就是 O(nlogn+nn)=O(nn)O(nlogn+n\sqrt{n})=O(n\sqrt{n})O(nlogn+nn?)=O(nn?)
打一個分割線,下面講講另一種寫法
注意到這個題很巧妙地預處理出了 fff 數組輔助莫隊的轉移,所以我們既然想到了 fff 數組,為什么不思考一下能否直接用 fff 數組計算區間的貢獻呢?
給定區間 [L,R][L,R][L,R],還是設 ppp 為區間最小值的位置,我們將總貢獻分成三份:區間 [L,R][L,R][L,R] 的貢獻 = 區間 [L,p)[L,p)[L,p) 的貢獻 + 區間 (p,R](p,R](p,R] 的貢獻 + (p?L+1)?(R?p+1)?a[p](p-L+1)*(R-p+1)*a[p](p?L+1)?(R?p+1)?a[p]
上述公式第三項 (p?L+1)?(R?p+1)?a[p](p-L+1)*(R-p+1)*a[p](p?L+1)?(R?p+1)?a[p] ,意思就是左端點位于 [L,p][L,p][L,p],同時右端點位于 [p,R][p,R][p,R] 的子區間的貢獻
這里我們以區間 (p,R](p,R](p,R] 的貢獻為例,分析一下該如何計算
前面的莫隊解法我們提到了,f[R]?f[p]f[R]-f[p]f[R]?f[p] 代表的是,右端點為 RRR,左端點位于 (p,R](p,R](p,R] 的子區間的貢獻
那么擴展到 f[R?1]?f[p]f[R-1]-f[p]f[R?1]?f[p] 是不是也可以代表,右端點為 R?1R-1R?1,左端點位于 (p,R?1](p,R-1](p,R?1] 的子區間的貢獻?
所以區間 (p,R](p,R](p,R] 的貢獻就可以寫成(f[R]?f[p])+(f[R?1]?f[p])+...+(f[p+1]?f[p])(f[R]-f[p])+(f[R-1]-f[p])+...+(f[p+1]-f[p])(f[R]?f[p])+(f[R?1]?f[p])+...+(f[p+1]?f[p])
設 g[i]=∑j=1jf[j]g[i]=\sum\limits_{j=1}^{j}f[j]g[i]=j=1∑j?f[j],也就是數組 fff 的一個前綴和,那么上述公式就等價于 g[R]?g[p]?f[p]?(R?p)g[R]-g[p]-f[p]*(R-p)g[R]?g[p]?f[p]?(R?p)
所以現在本題的時空復雜度就變成 RMQRMQRMQ 預處理的復雜度了,也就是 O(nlogn)O(nlogn)O(nlogn),且強制在線
代碼:
離線
在線
// Problem: P3246 [HNOI2016]序列 // Contest: Luogu // URL: https://www.luogu.com.cn/problem/P3246 // Memory Limit: 500 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org)// #pragma GCC optimize(2) // #pragma GCC optimize("Ofast","inline","-ffast-math") // #pragma GCC target("avx,sse2,sse3,sse4,mmx") #include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> #include<cassert> #include<bitset> #define lowbit(x) x&-x using namespace std; typedef long long LL; typedef unsigned long long ull; template<typename T> inline void read(T &x) {T f=1;x=0;char ch=getchar();while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();x*=f; } template<typename T> inline void write(T x) {if(x<0){x=~(x-1);putchar('-');}if(x>9)write(x/10);putchar(x%10+'0'); } const int inf=0x3f3f3f3f; const int N=1e5+100; int n,m,a[N],l[N],r[N],mmin[N][20],Log[N]; LL fl[N],fr[N],gl[N],gr[N]; int cmp(int x,int y) {return a[x]<a[y]?x:y; } int ask(int l,int r) {int k=Log[r-l+1];return cmp(mmin[l][k],mmin[r-(1<<k)+1][k]); } void init() {//單調棧stack<int>st;for(int i=1;i<=n;i++) {while(st.size()&&a[st.top()]>a[i]) st.pop();if(st.empty()) l[i]=0;else l[i]=st.top();st.push(i);}while(st.size()) st.pop();for(int i=n;i>=1;i--) {while(st.size()&&a[st.top()]>a[i]) st.pop();if(st.empty()) r[i]=n+1;else r[i]=st.top();st.push(i);}//RMQfor(int i=1;i<=n;i++)mmin[i][0]=i,Log[i]=log2(i);for(int i=1;i<20;i++)for(int j=1;j+(1<<i)-1<=n;j++)mmin[j][i]=cmp(mmin[j][i-1],mmin[j+(1<<(i-1))][i-1]);//dpfor(int i=1;i<=n;i++) fr[i]=fr[l[i]]+1LL*(i-l[i])*a[i],gr[i]=gr[i-1]+fr[i];for(int i=n;i>=1;i--) fl[i]=fl[r[i]]+1LL*(r[i]-i)*a[i],gl[i]=gl[i+1]+fl[i]; } int main() { #ifndef ONLINE_JUDGE // freopen("data.in.txt","r",stdin); // freopen("data.out.txt","w",stdout); #endif // ios::sync_with_stdio(false);read(n),read(m);for(int i=1;i<=n;i++) {read(a[i]);}init();while(m--) {int l,r;read(l),read(r);int p=ask(l,r);printf("%lld\n",1LL*(p-l+1)*(r-p+1)*a[p]+gr[r]-gr[p]-fr[p]*(r-p)+gl[l]-gl[p]-fl[p]*(p-l));}return 0; }總結
以上是生活随笔為你收集整理的洛谷 - P3246 [HNOI2016]序列(莫队+单调栈)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CodeForces - 817D Im
- 下一篇: HDU - 7009 树上游走(树的直径