1. 程式人生 > >【bzoj5089】最大連續子段和 分塊+單調棧

【bzoj5089】最大連續子段和 分塊+單調棧

我們 如果 一條直線 時間復雜度 支持 led 包括 每一個 a + b

題目描述

給出一個長度為 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

輸出

每個 Q 操作輸出一行,表示詢問區間的最大連續子段和。

樣例輸入

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】最大連續子段和 分塊+單調棧