bzoj5089 最大连续子段和 分块+复杂度分析+凸包
题目传送门
https://lydsy.com/JudgeOnline/problem.php?id=5089
题解
本来打算迟一点再写这个题解的,还有一个小问题没有弄清楚。
不过先写一下存个档吧。
如果只是单点修改,我们的常见做法是维护 \(ls, rs, s\) 表示前缀和最大值,后缀和最大值,区间最大子段和,然后进行区间合并,线段树维护。
但是这个在这里显然是行不通的,因为我们不是单点修改,我们需要考虑一个加标记对于整个连续段的影响。
对于一个长度为 \(k\) 的子段,初始的时候它的和为 \(b\),如果增加量为 \(x\),那么现在的值应该是 \(kx+b\)。
很容易发现,这个现在的值与 \(x\) 是线性关系,然后我们在一个段中,显然是要把所有的这样的直线在某一个 \(x\) 的位置取 \(\max\)。
所以可以用凸包来维护一下这个东西。每一次询问的时候,因为加标记的值一定是递增的,所以只需要在凸包上移动指针就可以了。
可以看出,如果一段的长度为 \(b\),那么构造这样的凸包的复杂度为 \(O(b^2)\)。
但是如果使用线段树的话,被影响到的不仅用整段,还有这些整段的所有祖先,而加标记在祖先上却不是满的,所以要重构的段有 \(\log\) 段,每一次重构的复杂度为 \(O(b^2)\),而在线段树上,\(b\) 最长可以达到 \(n\)。所以使用线段树不是一个明智的选择。
那么我们考虑使用每一段的长度有保证的分块。
设每一块的长度为 \(b\)。
对于一开始的构造操作,需要枚举每一块来构造,时间复杂度为 \(O(\frac nb \cdot b^2) = O(nb)\)。
对于修改操作,如果是整块可以直接打标记,如果是零散的块,就直接暴力重构,只需要重构两块,每一次重构 \(O(b^2)\),所以复杂度为 \(O(\frac nb + b^2)\)。
对于查询操作,如果是整块直接在凸包上移动指针,显然从始至终,指针移动的总幅度不超过 \(b\),所以可以看成均摊 \(O(1)\),对于散块,也是直接暴力做最大字段和,复杂度 \(O(b)\)。因此这里的复杂度为 \(O(\frac nb + b)\)。
我们取上面复杂度最高的 \(O(\frac nb +b^2)\) 来分析。我们需要让这个东西最小,根据基本不等式,当 \(\frac nb +b^2\) 的时候可以满足这个条件。
所以 \(b\) 应该取 \(\sqrt[3]n\),即 \(n^{\frac 13}\)。
那么初始化的复杂度为 \(O(n^{\frac 43})\),单次询问的复杂度为 \(O(n^{\frac 23})\),单次修改的复杂度也是 \(O(n^{\frac 23})\)。
但是我还有一个问题没有解决:
指针在凸包上的总移动次数不超过 \(O(b)\) 是因为加的一直是正数,也就是加标记越来越大。
但是被暴力重构的块的凸包形态会改变啊。这个会不会破坏上面的复杂度分析呢。
不考虑上面的这个问题,这个题目的时间复杂度为 \(O(n^{\frac 43}+mn^{\frac 23})\),因为 \(n, m\leq 50000\),勉强可以通过。
但是我怎么比暴力还慢啊。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
	int f = 0, c;
	while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
	x = c & 15;
	while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
	f ? x = -x : 0;
}
const int N = 50000 + 7;
const int B = 40 + 7;
#define bl(x) (((x) - 1) / blo + 1)
#define st(x) (((x) - 1) * blo + 1)
#define ed(x) std::min((x) * blo, n)
int n, m, blo;
int a[N], add[B * B];
ll sum[B * B];
struct Line {
	ll k, b;
	inline Line() {}
	inline Line(const ll &k, const ll &b) : k(k), b(b) {}
	inline ll get_y(const int &x) { return k * x + b; }
	inline bool operator < (const Line &l) const { return k < l.k || (k == l.k && b < l.b); }
};
inline double crs(const Line &l1, const Line &l2) { return ((double)l2.b - l1.b) / (l1.k - l2.k); }
struct Convex {
	Line a[B], q[B];
	int n, tl, now;
	inline void build() {
		std::sort(a + 1, a + n + 1);
		tl = 0;
		for (int i = 1; i <= n; ++i) {
			while (tl > 1 && crs(q[tl - 1], a[i]) <= crs(q[tl - 1], q[tl])) --tl;
			q[++tl] = a[i];
		}
	}
	inline void reset(int nn) { now = 1, n = nn; }
	inline ll get(int p) {
		while (now <= tl) {
			if (now == tl || p <= crs(q[now], q[now + 1])) return q[now].get_y(p);
			else ++now;
		}
		return assert(0), 0;
	}
} c[B * B], spre[B * B], ssuf[B * B];
inline void rebuild(int i, int l = 1, int r = 0, int k = 0) {
	sum[i] = 0;
	for (int j = st(i); j <= ed(i); ++j) a[j] += add[i], sum[i] += a[j];
	add[i] = 0;
	for (int j = l; j <= r; ++j) a[j] += k, sum[i] += k;
	int len = ed(i) - st(i) + 1;
	ll s1 = 0, s2 = 0;
	c[i].reset(len), spre[i].reset(len), ssuf[i].reset(len);
	for (int j = 1; j <= len; ++j) {
		ll mx = -0x7fffffffffffffff, s = 0;
		for (int k = st(i); k <= st(i) + j - 1; ++k) s += a[k];
		mx = s;
		for (int k = st(i) + 1; k <= ed(i) - j + 1; ++k) smax(mx, s += a[k + j - 1] - a[k - 1]);
		s1 += a[st(i) + j - 1], s2 += a[ed(i) - j + 1];
		c[i].a[j].k = j, c[i].a[j].b = mx;
		spre[i].a[j].k = j, spre[i].a[j].b = s1;
		ssuf[i].a[j].k = j, ssuf[i].a[j].b = s2;
	}
	c[i].build(), spre[i].build(), ssuf[i].build();
}
inline void build() {
	for (int i = 1; i <= bl(n); ++i) rebuild(i);
}
inline void qadd(int l, int r, int k) {
	if (bl(l) == bl(r)) return rebuild(bl(l), l, r, k);
	for (int i = bl(l) + 1; i < bl(r); ++i) add[i] += k, sum[i] += k * (ed(i) - st(i) + 1ll);
	rebuild(bl(l), l, ed(bl(l)), k), rebuild(bl(r), st(bl(r)), r, k);
}
inline ll qans(int l, int r) {
	if (bl(l) == bl(r)) {
		ll ans = 0, s = 0, b = bl(l);
		for (int i = l; i <= r; ++i) {
			s = std::max(s + a[i] + add[b], (ll)a[i] + add[b]);
			smax(ans, s);
		}
		return ans;
	}
	ll ans = 0, s = 0;
	for (int i = l; i <= ed(bl(l)); ++i) s = std::max(s + a[i] + add[bl(i)], (ll)a[i] + add[bl(i)]), smax(ans, s);
	for (int i = bl(l) + 1; i < bl(r); ++i) {
		smax(ans, s + spre[i].get(add[i]));
		smax(ans, c[i].get(add[i]));
		s = std::max(s + sum[i], ssuf[i].get(add[i]));
	}
	for (int i = st(bl(r)); i <= r; ++i) s = std::max(s + a[i] + add[bl(i)], (ll)a[i] + add[bl(i)]), smax(ans, s);
	return ans;
}
inline void work() {
	build();
	while (m--) {
		int l, r, x;
		static char s[5];
		scanf("%s", s);
		if (*s == 'Q') {
			read(l), read(r);
			printf("%lld\n", qans(l, r));
		} else read(l), read(r), read(x), qadd(l, r, x);
	}
}
inline void init() {
	read(n), read(m);
	blo = pow(n, 1.0 / 3);;
	for (int i = 1; i <= n; ++i) read(a[i]);
}
int main() {
#ifdef hzhkk
	freopen("hkk.in", "r", stdin);
#endif
	init();
	work();
	fclose(stdin), fclose(stdout);
	return 0;
}
												
											bzoj5089 最大连续子段和 分块+复杂度分析+凸包的更多相关文章
- 【bzoj5089】最大连续子段和  分块+单调栈维护凸包
		
题目描述 给出一个长度为 n 的序列,要求支持如下两种操作: A l r x :将 [l,r] 区间内的所有数加上 x : Q l r : 询问 [l,r] 区间的最大连续子段和. 其中,一 ...
 - BZOJ5089 最大连续子段和(分块)
		
假设所有操作都是对整个序列的.考虑每个子区间,区间和与其被加的值构成一次函数关系.最大子段和相当于多个子区间取最大值,答案显然就在这些一次函数构成的下凸壳上.如果预处理出凸壳,只要在凸壳上暴力跳就可以 ...
 - BZOJ5089: 最大连续子段和
		
维护一个序列支持以下操作:区间加,区间求最大子段和.n<=50000,m<=50000. 我TM再也不写分块了... 先分块,对于块整体加的操作,假设块里面有若干二元组(x,y),表示一个 ...
 - HDU 1003:Max Sum(DP,连续子段和)
		
Max Sum Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Su ...
 - TZOJ 4024 游戏人生之梦幻西游(连续子段和绝对值最小)
		
塔神酷爱玩梦幻西游这款游戏,这款游戏以著名的章回小说<西游记>故事为背景,透过Q版的人物,营造出浪漫的网络游戏风格.塔神以追求天下无敌为目标,从一个默默无闻的菜鸟,打拼到了登峰造极的大师, ...
 - HDOJ-1003 Max Sum(最大连续子段 动态规划)
		
http://acm.hdu.edu.cn/showproblem.php?pid=1003 给出一个包含n个数字的序列{a1,a2,..,ai,..,an},-1000<=ai<=100 ...
 - HPU 1007: 严格递增连续子段(贪心)
		
1007: 严格递增连续子段 [模拟] 时间限制: 1 Sec 内存限制: 128 MB提交: 244 解决: 18 统计 题目描述 给定一个有NN个正整数组成的序列,你最多可以改变其中一个元素,可以 ...
 - HDU 1003 最大连续子段和
		
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1003 Max Sum Time Limit: 2000/1000 MS (Java/Others)M ...
 - [题解](线段树最大连续子段和)POJ_3667_Hotel
		
题意:1.求一个最靠左的长x的区间全部为0,并修改为1,输出这个区间的左端点 2.修改一个区间为0 实际上是维护最大连续子段和,原来也写过 大概需要维护一个左/右最大子段和,当前这段最大子段长,再维护 ...
 
随机推荐
- TP3.2写分页
			
用TP3.2写分页 手册上说的好难懂,我自己去网上找资料 ,现在整理一下,以后可能会用: 在Think下面有Page.class.php类: 我在这个下面放了一个function.php的(算是类吧又 ...
 - 分布式消息队列 Celery 的最佳实践
			
目录 目录 不使用数据库作为 Broker 不要过分关注任务结果 实现优先级任务 应用 Worker 并发池的动态扩展 应用任务预取数 保持任务的幂等性 应用任务超时限制 善用任务工作流 合理应用 a ...
 - Delphi XE2 之 FireMonkey 入门(36) - 控件基础: TForm
			
Delphi XE2 之 FireMonkey 入门(36) - 控件基础: TForm 当我第一次读取 Form1.StyleLookup 并期待出现 "formstyle" 时 ...
 - Chrome 浏览器添加跨域支持
			
开发前端本地项目时,涉及到与后端服务器的通信联调,在使用 ajax 时由于浏览器的安全策略不允许跨域.一种方式是本地搭建转发服务器,今天又 GET 到一种更直接的方式,在 Chrome 浏览器开启时添 ...
 - RabbitMQ使用(上)
			
1. 说明 在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的但是执行起来比较较耗时的地方, ...
 - P站图片下载工具。
			
下载 Pixiv 的图片比较麻烦,就做了这么个东西. 主要就是用 HttpWebRequest HttpWebResponse 下载了网页的 html 代码然后截取里面的内容.代码上传到了文件里. p ...
 - 【MM系列】SAP 创建工厂
			
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP 创建工厂 前言部分 大家 ...
 - 【监控实践】【4.1】利用trace实现阻塞跟踪和慢查询跟踪
			
原文:https://blog.csdn.net/kk185800961/article/details/49252037 分享个SQLServer profiler 的一个技巧吧.很早用过,忘记总结 ...
 - H264 RTP包解析
			
1. 预备 视频: 由一副副连续的图像构成,由于数据量比较大,因此为了节省带宽以及存储,就需要进行必要的压缩与解压缩,也就是编解码. h264裸码流: 对一个图像或者一个视频序列进行压缩,即产生码流 ...
 - getopt_long函数解析命令行参数
			
转载:http://blog.csdn.net/hcx25909/article/details/7388750 每一天你都在使用大量的命令行程序,是不是感觉那些命令行参数用起来比较方便,他们都是使用 ...