bzoj5089最大连续子段和 分块+单调栈

Posted GXZlegend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

输出

每个 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最大连续子段和 分块+单调栈的主要内容,如果未能解决你的问题,请参考以下文章

算法笔记--最大子段和问题

最大连续子段和

最大连续子段和

p1115 最大子段和(线段树)

最大连续子数组和(最大子段和)

[题解](线段树最大连续子段和)POJ_3667_Hotel