【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;
}
时间: 2024-08-28 13:53:11

【bzoj5089】最大连续子段和 分块+单调栈的相关文章

bzoj5089 最大连续子段和 分块+复杂度分析+凸包

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5089 题解 本来打算迟一点再写这个题解的,还有一个小问题没有弄清楚. 不过先写一下存个档吧. 如果只是单点修改,我们的常见做法是维护 \(ls, rs, s\) 表示前缀和最大值,后缀和最大值,区间最大子段和,然后进行区间合并,线段树维护. 但是这个在这里显然是行不通的,因为我们不是单点修改,我们需要考虑一个加标记对于整个连续段的影响. 对于一个长度为 \(k\) 的子段,初始的时候它的和为

【11.2晚校内测试】【装桶模拟】【单调栈】

真的是fo了,晚上还来一次测试...... mister[问题描述] 不久前 Mister 从太空中探测到一个奇怪的信号,他开始研究这个信号. 经过一些变换后,这个信号变成了长度为 n 的排列或者它的循环移位.对于进一步的研究 Mister 需要一些数据分析,这就是为什么他决定选择这个排列的循环移位,它有最小的可 能偏差.我们把排列的偏差定义为 求一个可能偏差最小的排列 p 的循环移位. 让我们表示 k(0≤k < n)的循环移位排列 p 的变化需要达到这种转变,例如: k = 0: 变成 p1

洛谷10月月赛Round.1| P3400 仓鼠窝[单调栈]

题目描述 萌萌哒的Created equal是一只小仓鼠,小仓鼠自然有仓鼠窝啦. 仓鼠窝是一个由n*m个格子组成的行数为n.列数为m的矩阵.小仓鼠现在想要知道,这个矩阵中有多少个子矩阵!(实际上就是有多少个子长方形嘛.)比如说有一个2*3的矩阵,那么1*1的子矩阵有6个,1*2的子矩阵有4个,1*3的子矩阵有2个,2*1的子矩阵有3个,2*2的子矩阵有2个,2*3的子矩阵有1个,所以子矩阵共有6+4+2+3+2+1=18个. 可是仓鼠窝中有的格子被破坏了.现在小仓鼠想要知道,有多少个内部不含被破

【POJ3658】【USACO 2008 Jan Gold】 2.Artificial Lake人工湖 单调栈

人工湖 Time Limit: 1 Sec  Memory Limit: 128 MB Description 夏日那让人喘不过气的酷热将奶牛们的烦躁情绪推到了最高点.最终,FJ 决定建一个人工湖供奶牛消暑之用.为了使湖看起来更加真实,FJ决定将湖的 横截面建成N(1 <= N <= 100,000)个连续的平台高低错落的组合状,所有的平台 从左到右按1..N依次编号.当然咯,在湖中注入水后,这些平台都将被淹没. 平台i在设计图上用它的宽度W_i(1 <= W_i <= 1,000

hdu 4923 Room and Moor (单调栈+思维)

题意: 给一个0和1组成的序列a,要构造一个同样长度的序列b.b要满足非严格单调,且 值为0到1的实数.最后使得  sum((ai-bi)^2)最小. 算法: 首先a序列开始的连续0和末尾的连续1是可以不考虑的.因为只要b序列对应开头为0. 末尾为1,既不影响单调性又能使对应的(ai-bi)^2=0. 然后, 先找111100.11100.10这样以1开始以0结束的序列块.每一块对应的b值相等且均为 这一块的平均值,即1的个数/0和1的总个数. 但是要满足b的单调性,则我们用栈来维护,如果后面一

hdu_1506:Largest Rectangle in a Histogram 【单调栈】

题目链接 对栈的一种灵活运用吧算是,希望我的注释写的足够清晰.. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 5 const int N=100010; 6 int Stack[N]; //Stack[]为单调栈(即每次能入栈的元素值必比栈顶元素(若存在)要大) 7 int len[N]; //len[]与Stack[]同步 ,记录的是从Stack[]中对应元素起向左最大可延伸的宽度 8

UVa 1451 (数形结合 单调栈) Average

题意: 给出一个01串,选一个长度至少为L的连续子串,使得串中数字的平均值最大. 分析: 能把这道题想到用数形结合,用斜率表示平均值,我觉得这个想法太“天马行空”了 首先预处理子串的前缀和sum,如果在坐标系中描出(i, sum[i])这些点的话. 所求的平均值就是两点间的斜率了,具体来说,在连续子串[a, b]中,有sum[b]-sum[a-1]个1,长度为b-a+1,所以平均值为(sum[b]-sum[a-1])/(b-a+1) 所以就把问题转化为:求两点横坐标之差至少为L-1,能得到的最大

CodeForces 548D 单调栈

Mike and Feet Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Status Practice CodeForces 548D Appoint description: Description Mike is the president of country What-The-Fatherland. There are n bears living in this

POJ 3415 Common Substrings(长度不小于k 的公共子串的个数--后缀数组+单调栈优化)

题意:给定两个字符串A 和B,求长度不小于k 的公共子串的个数(可以相同). 样例1: A="xx",B="xx",k=1,长度不小于k 的公共子串的个数是5. 样例2: A ="aababaa",B ="abaabaa",k=2,长度不小于k 的公共子串的个数是22. 思路: 如果i后缀与j后缀的LCP长度为L, 在L不小于K的情况下, 它对答案的贡献为L - K + 1. 于是我们可以将两个串连起来, 中间加个奇葩的分隔符