【bzoj5089】最大連續子段和 分塊+單調棧
題目描述
給出一個長度為 n 的序列,要求支持如下兩種操作: A l r x :將 [l,r] 區間內的所有數加上 x ; Q l r : 詢問 [l,r] 區間的最大連續子段和。 其中,一個區間的最大連續子段和指的是:該區間所有子區間的區間和中的最大值(本題中子區間包括空區間,區間和為 0 )。輸入
第一行兩個整數 n、m,表示序列的長度以及操作的數目。 之後的 m 行,每行輸入一個操作,含義如題目所述。保證操作為 A l r x 或 Q l r 之一。 對於 30% 的數據,n,m≤300 ; 對於 60% 的數據,n,m≤1000 ; 對於 100% 的數據,1≤n,m≤50000, |a_i |≤10^9, 1≤x≤40000, 1≤l,r≤n輸出
樣例輸入
5 5
2 -3 0 4 -7
Q 1 2
Q 1 5
A 2 3 2
Q 2 5
Q 1 3
樣例輸出
2
4
6
3
題解
暴力 分塊+單調棧維護凸包
考慮這個問題的一個簡化版本:對整個序列區間加,對整個序列查詢最大連續子段和。
我們對於每一個子區間,考慮區間和 $y$ 與區間加的總值 $x$ 的關系,顯然是一個一次函數關系,斜率為區間長度,截距為原來的區間和。容易發現對於每個 $x$ 取最上方的直線(即選擇最大連續子段和)的話,最終形成的是一個第一象限的下凸包的形式(可以參考 [JLOI2013]賽車)。
因此我們首先把這個下凸包求出來:每個斜率保留最上方的一條(即同一區間長度取區間和最大的),然後按照斜率從小到大加入當前直線、使用單調棧彈出不合法直線。這樣我們就求出了這個上凸包。對於整體加和操作時,判斷當前到達哪一條直線即可。由於加的只有正整數,因此這個移動過程最多只能進行 $n$ 次。
那麽如果操作不是對整個序列進行的呢?可以考慮把序列分塊,加和時整塊如上處理,零碎部分暴力重構;查詢時直接區間合並。由於要區間合並,因此還要維護從左開始的最大連續段和、從右開始的最大連續段和。處理方法與最大連續子段和相同。
分析一下這樣做的時間復雜度:
設塊的大小為 $si$
對於預處理操作,重構每個塊的時間復雜度為 $O(si^2)$ ,重構 $\frac n{si}$ 個塊的時間復雜度為 $O(n·si)$ ;
對於修改操作,一個塊只有重構以後才能產生貢獻 $O(si)$,重構的時間復雜度為 $O(si^2)$,每次修改操作只會重構最多兩個塊,因此單次修改操作的時間復雜度為 $O(si^2)$ ;
對於查詢操作,整塊拿出來區間信息是 $O(1)$ 的,因此查詢操作的時間復雜度為 $O(\frac n{si}+si)$ 。
因此總的時間復雜度為 $O(m(\frac n{si}+si^2))$
根據均值不等式,當 $\frac n{si}=si^2$ 時這個復雜度最小,此時 $si=\sqrt[3]n$(實際上 $si=2\sqrt[3]n$ 時最優)
時間復雜度 $O(n^{\frac 53})$
然而暴力可以過我也是醉了。。。沒辦法卡不住啊╮(╯▽╰)╭
話說本題我原來還打算出一個帶區間減的,只需要在凸包上二分即可,然而感覺更跑不過暴力於是就沒出。。。
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; inline ll cdiv(ll x , ll y) { return (x + y - 1) / y; } inline void solve(int c , ll *v , int *p , int &s , int &now) { int i; for(now = 1 , s = i = 0 ; i <= c ; i ++ ) { while(s && v[i] >= v[p[s]]) s -- ; while(s > 1 && (i - p[s]) * cdiv(v[p[s - 1]] - v[p[s]] , p[s] - p[s - 1]) >= v[p[s]] - v[i]) s -- ; p[++s] = i; } } inline void add(ll a , ll *v , int *p , int s , int &now) { while(now < s && p[now + 1] * a + v[p[now + 1]] >= p[now] * a + v[p[now]]) now ++ ; } struct data { int c , lp[80] , rp[80] , tp[80] , sl , sr , st , nowl , nowr , nowt; ll v[80] , sum , a , lv[80] , rv[80] , tv[80]; inline void build() { int i , j; ll s; sum = 0; for(i = 1 ; i <= c ; i ++ ) sum += v[i]; for(i = 1 ; i <= c ; i ++ ) lv[i] = lv[i - 1] + v[i]; for(i = 1 ; i <= c ; i ++ ) rv[i] = rv[i - 1] + v[c - i + 1]; for(i = 1 ; i <= c ; i ++ ) tv[i] = -1ll << 62; for(i = 1 ; i <= c ; i ++ ) { s = 0; for(j = i ; j <= c ; j ++ ) s += v[j] , tv[j - i + 1] = max(tv[j - i + 1] , s); } solve(c , lv , lp , sl , nowl); solve(c , rv , rp , sr , nowr); solve(c , tv , tp , st , nowt); } inline void update(ll v) { a += v; add(a , lv , lp , sl , nowl); add(a , rv , rp , sr , nowr); add(a , tv , tp , st , nowt); } }b[800]; ll a[50010]; char str[5]; int main() { int n , m , si , i , l , r , t; ll x , al , ar , at , as , tl , tr , tt , ts; scanf("%d%d" , &n , &m) , si = (int)cbrt(n) << 1; for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &a[i]); for(i = 0 ; i <= (n - 1) / si ; i ++ ) { for(b[i].c = 1 ; b[i].c <= si && b[i].c + i * si <= n ; b[i].c ++ ) b[i].v[b[i].c] = a[i * si + b[i].c]; b[i].c -- , b[i].build(); } while(m -- ) { scanf("%s%d%d" , str , &l , &r); if(str[0] == ‘A‘) { scanf("%lld" , &x); if((l - 1) / si == (r - 1) / si) { t = (l - 1) / si; for(i = l - t * si ; i <= r - t * si ; i ++ ) b[t].v[i] += x; for(i = 1 ; i <= b[t].c ; i ++ ) b[t].v[i] += b[t].a; b[t].a = 0 , b[t].build(); } else { t = (l - 1) / si; for(i = l - t * si ; i <= b[t].c ; i ++ ) b[t].v[i] += x; for(i = 1 ; i <= b[t].c ; i ++ ) b[t].v[i] += b[t].a; b[t].a = 0 , b[t].build(); for(i = (l - 1) / si + 1 ; i < (r - 1) / si ; i ++ ) b[i].update(x); t = (r - 1) / si; for(i = 1 ; i <= r - t * si ; i ++ ) b[t].v[i] += x; for(i = 1 ; i <= b[t].c ; i ++ ) b[t].v[i] += b[t].a; b[t].a = 0 , b[t].build(); } } else { as = al = ar = at = 0; if((l - 1) / si == (r - 1) / si) { t = (l - 1) / si; for(i = l - t * si ; i <= r - t * si ; i ++ ) { x = b[t].v[i] + b[t].a; at = max(at , ar + x); al = max(al , as + x); ar = max(ar + x , 0ll); as += x; } } else { t = (l - 1) / si; for(i = l - t * si ; i <= b[t].c ; i ++ ) { x = b[t].v[i] + b[t].a; at = max(at , ar + x); al = max(al , as + x); ar = max(ar + x , 0ll); as += x; } for(i = (l - 1) / si + 1 ; i < (r - 1) / si ; i ++ ) { tl = b[i].lp[b[i].nowl] * b[i].a + b[i].lv[b[i].lp[b[i].nowl]]; tr = b[i].rp[b[i].nowr] * b[i].a + b[i].rv[b[i].rp[b[i].nowr]]; tt = b[i].tp[b[i].nowt] * b[i].a + b[i].tv[b[i].tp[b[i].nowt]]; ts = b[i].sum + b[i].c * b[i].a; at = max(at , max(tt , ar + tl)); al = max(al , as + tl); ar = max(tr , ts + ar); as += ts; } t = (r - 1) / si; for(i = 1 ; i <= r - t * si ; i ++ ) { x = b[t].v[i] + b[t].a; at = max(at , ar + x); al = max(al , as + x); ar = max(ar + x , 0ll); as += x; } } printf("%lld\n" , at); } } return 0; }
【bzoj5089】最大連續子段和 分塊+單調棧