\[ Preface \]數據結構
沒有 Preface。
\[ Description \]
維護一個長度爲 \(n\) 的數列 \(A\) ,須要支持如下操做:函數
0 x y
將 \(A_x\) 改成 \(y\) 。ui
1 x y
求 \(\max\limits_{x \leq l \leq r \leq y}{\sum_{i=l}^rA[i]}\) 。spa
\[ Solution \]code
區間最大子段和 是一個很是經典的問題。遞歸
對於 總體最大子段和 來講,通常有 \(O(n)\) 的 貪心 和 分治 作法,咱們討論的重點是 分治 作法。ip
\(~\)it
假設當前需求解最大子段和的區間是 \([l,r]\) ,令 \(mid=\left\lfloor\dfrac{l+r}{2}\right\rfloor\)io
咱們套路地把 \([l,r]\) 分紅 \([l,mid]\) 和 \([mid+1,r]\) ,來進行分治求解:class
\(~\)
首先對於形如 \([l,l]\) 的區間,就十分好處理了,這裏很少說。
\(~\)
接下來,考慮下最大子段和滿不知足 區間可加性 ?(\([l,mid]\) 和 \([mid+1,r]\) 的最大子段和可否推至 \([l,r]\))
顯然,只維護一個最大子段和,對 左子區間 和 右子區間 的最大子段和取個 \(\max\) 是不能維護最大子段和的,由於其漏掉了 最大子段和同時包含左子區間和右子區間 的狀況。
那麼對於剩下的這一種狀況,它是一定通過中點 \(mid\) 的,那這種狀況的最大段就是 左子區間從右端點向左走的最大段 與 右子區間從左端點向右走的最大段 的並集,其值爲 左子區間的後綴最大子段和 \(+\) 右子區間的前綴最大字段和 。
那咱們再維護 前 \(/\) 後 綴最大子段和 ,對其三者取 \(\max\) ,最大子段和就知足區間可加性了。
維護 前 \(/\) 後 綴最大子段和 依舊能夠分紅 通過 \(mid\) \(/\) 不通過 \(mid\) 來討論。
之前綴最大子段和爲例,若通過 \(mid\) ,則最大段爲 左子區間 與 右子區間從左端點向右走的最大段 的並集,其值爲 左子區間和 \(+\) 右子區間的前綴最大子段和 ;若不通過 \(mid\) ,則最大段爲 左子區間從左端點向右走的最大段 。兩者取個 \(\max\) 便可。後綴最大子段和同理。
那咱們再維護個 區間和 ,那 最大子段和 ,前 \(/\) 後 綴最大子段和 就都知足區間可加性了。
至於 區間和 \(......\) ,這玩意直接加就好了。
(上述內容你們能夠本身畫圖感性理解一下
\(~\)
求解過程
約定變量:
\(sum\) : 區間和
\(lmax\) : 區間前綴最大子段和
\(rmax\) : 區間後綴最大子段和
\(wmax\) : 區間最大子段和
設函數 \(ask(l,r)\) 求的是關於區間 \([l,r]\) 的一個四元組\((\) \(sum\), \(lmax\), \(rmax\), \(wmax\) \()\)
首先有一個遞歸邊界 \(l=r\) ,此時這四個元素均爲 \(A_l\) 。
那對於通常狀況,令 \(lc=ask(l,mid),rc=ask(mid+1,r)\) ,則有:
\[ self.sum=lc.sum+rc.sum \]
\[ self.lmax=\max(lc.lmax,lc.sum+rc.lmax) \]
\[ self.rmax=\max(rc.rmax,rc.sum+lc.rmax) \]
\[ self.wmax=\max(lc.wmax,rc.wmax,lc.rmax+rc.lmax) \]
此時 \(self\) 即爲 \(ask(l,r)\) 。
struct data{ int sum; int lmax; int rmax; int wmax; }; data ask(int l,int r) { data self; if(l==r) { self.sum=self.lmax=self.rmax=self.wmax=A[l]; return self; } int mid=(l+r)/2; data lc=ask(l,mid),rc=ask(mid+1,r); self.sum=lc.sum+rc.sum; self.lmax=max(lc.lmax,lc.sum+rc.lmax); self.rmax=max(rc.rmax,rc.sum+lc.rmax); self.wmax=max(max(lc.wmax,rc.wmax),lc.rmax+rc.lmax); return self; }
\(~\)
而後你會發現,若對於每一個詢問都調用一次 ask(l,r) 會穩穩 T 掉
那我 bb 這麼多有什麼用呢
你們仔細想一想,這個分治的過程像不像某個數據結構呢?
線段樹?
線段樹!
是的,用線段樹維護,每一個節點保存的是該節點所表明的區間 \([l,r]\) 的 \((\) \(sum\), \(lmax\), \(rmax\), \(wmax\) \()\) 。
剩下的都是一些線段樹基本操做了。
\[ Code \]
#include<cstdio> #include<algorithm> #define RI register int using namespace std; const int SIZE=500100; int n,m; int a[SIZE]; struct SegmentTree{ int l,r; int sum; int lmax; int rmax; int dat; }t[SIZE*4]; void build(int p,int l,int r) { t[p].l=l;t[p].r=r; if(l==r){t[p].sum=t[p].lmax=t[p].rmax=t[p].dat=a[l];return;} int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); t[p].sum=t[p*2].sum+t[p*2+1].sum; t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax); t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax); t[p].dat=max(max(t[p*2].dat,t[p*2+1].dat),t[p*2].rmax+t[p*2+1].lmax); } void change(int p,int x,int val) { if(t[p].l==t[p].r){t[p].sum=t[p].lmax=t[p].rmax=t[p].dat=val;return;} int mid=(t[p].l+t[p].r)/2; if(x<=mid)change(p*2,x,val); else change(p*2+1,x,val); t[p].sum=t[p*2].sum+t[p*2+1].sum; t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax); t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax); t[p].dat=max(max(t[p*2].dat,t[p*2+1].dat),t[p*2].rmax+t[p*2+1].lmax); } SegmentTree ask(int p,int l,int r) { if(l<=t[p].l&&t[p].r<=r)return t[p]; int mid=(t[p].l+t[p].r)/2; if(l<=mid&&mid<r) { SegmentTree lc=ask(p*2,l,r),rc=ask(p*2+1,l,r),self; self.sum=self.lmax=self.rmax=self.dat=0; self.sum=lc.sum+rc.sum; self.lmax=max(lc.lmax,lc.sum+rc.lmax); self.rmax=max(rc.rmax,rc.sum+lc.rmax); self.dat=max(max(lc.dat,rc.dat),lc.rmax+rc.lmax); return self; } if(l<=mid) return ask(p*2,l,r); if(mid<r) return ask(p*2+1,l,r); } int main() { scanf("%d",&n); for(RI i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); scanf("%d",&m); while(m--) { char op[2]; int l,r; scanf("%s%d%d",op,&l,&r); switch(op[0]) { case '1':{ if(l>r)swap(l,r); printf("%d\n",ask(1,l,r).dat); break; } case '0':{ change(1,l,r); break; } } } return 0; }
\[ Thanks \ for \ watching \]