题目描述

给出一个长度为 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】最大连续子段和 分块+单调栈维护凸包的更多相关文章

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

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5089 题解 本来打算迟一点再写这个题解的,还有一个小问题没有弄清楚. 不过先写一下存个档吧. ...

  2. [CSP-S模拟测试]:A(单调栈维护凸包+二分答案)

    题目传送门(内部题150) 输入格式 第一行两个整数$N,Q$. 接下来的$N$行,每行两个整数$a_i,b_i$. 接下来的$Q$行,每行一个整数$x$. 输出格式 对于每个询问,输出一行一个整数表 ...

  3. Lost My Music:倍增实现可持久化单调栈维护凸包

    题目就是求树上每个节点的所有祖先中(ci-cj)/(dj-di)的最小值. 那么就是(ci-cj)/(di-dj)的最大值了. 对于每一个点,它的(ci,di)都是二维坐标系里的一个点 要求的就是祖先 ...

  4. HDU 5033 (单调栈维护凸包) Building

    题意: 一个人在x轴上,他的左右两侧都有高楼,给出楼的横坐标Xi和高度Hi还有人的位置pos,求人所能看到的天空的最大角度. 分析: 将建筑物和人的位置从左到右排序,对于每个位置利用栈求一次人左边建筑 ...

  5. HDU 5033 Building(单调栈维护凸包)

    盗张图:来自http://blog.csdn.net/xuechelingxiao/article/details/39494433 题目大意:有一排建筑物坐落在一条直线上,每个建筑物都有一定的高度, ...

  6. CF535E Tavas and Pashmaks 单调栈、凸包

    传送门 题意:有一场比赛,$N$个人参加.每个人有两种参数$a,b$,如果存在正实数$A,B$使得$\frac{A}{a_i} + \frac{B}{b_i}$在$i=x$处取得最大值(可以有多个最大 ...

  7. BZOJ 2388--旅行规划(分块&单调栈&二分)

    2388: 旅行规划 Time Limit: 50 Sec  Memory Limit: 128 MBSubmit: 405  Solved: 118[Submit][Status][Discuss] ...

  8. 51nod 1254 最大子段和 V2 ——单调栈

    N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的.当所给的整数均为负数时和为 ...

  9. LOJ #2769 -「ROI 2017 Day 1」前往大都会(单调栈维护斜率优化)

    LOJ 题面传送门 orz 斜率优化-- 模拟赛时被这题送走了,所以来写篇题解( 首先这个最短路的求法是 trivial 的,直接一遍 dijkstra 即可( 重点在于怎样求第二问.注意到这个第二问 ...

随机推荐

  1. elk6.3.2在线安装中文分词工具IK

    1.进入ES目录并执行安装(注意版本号改成你需要的版本) cd /usr/share/elasticsearch ./bin/elasticsearch-plugin install https:// ...

  2. Redis系列三 Redis数据类型

    一 .Redis的五大数据类型 1.String(字符串) string是redis最基本的数据类型,可以理解成与 Memached一模一样的数据类型,一个key对应一个value. string 类 ...

  3. hdu1596find the safest road(floyd)

    find the safest road Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Ot ...

  4. Qt-QML-自定义个自己的文本Text

    好久都没有正经的更新自己的文章了,这段时间也辞职了,听了小爱的,准备买个碗,自己当老板,下面请欣赏效果图 这个界面布局就是自己是在想不到啥了,按照常规汽车导航的布局布局了一下,主要看内容哈,看看这个文 ...

  5. MySQL☞自连接

    自连接:一张表中根据自身列之间的关联关系,自己跟自己链接. A.创建一个user表,且插入数据,数据如下: B.分析: 把user表看成两张表,一张员工表,一张领导表,发现员工表中lead(领导编号) ...

  6. HDU-1496(哈希表)

    Hash入门第一题 题意: 问题描述 考虑具有以下形式的方程: a * x1 ^ 2 + b * x2 ^ 2 + c * x3 ^ 2 + d * x4 ^ 2 = 0 a,b,c,d是来自区间[- ...

  7. 安迪的第一个字典 (Andy's First Dictionary,UVa10815)

    题目描述: #include<iostream> #include<string> #include<set> #include<sstream> us ...

  8. Git版本库工作流程图想

    对照廖雪峰的教程,发现有很多难以理解的地方,画了一个图想方便以后参考 首先两个基本命令反应了版本库最本质的工作流程,后面的命令其实都基于此git add 把文件修改添加到暂存区git commit 在 ...

  9. JavaScript 作用域链范例

    函数在执行的过程中,先从自己内部找变量 如果找不到,再从创建当前函数所在的作用域去找,以此往上 注意找的是变量的当前状态 范例 例1 var a=1 function fn1() { function ...

  10. LeetCode 100——相同的树

    1. 题目 2. 解答 针对两棵树的根节点,有下列四种情况: p 和 q 都为空,两棵树相同: p 不为空 q 为空,两棵树不相同: p 为空 q 不为空,两棵树不相同: p 和 q 都不为空,如果两 ...