码迷,mamicode.com
首页 > 其他好文 > 详细

P4192 旅行规划(分块+凸包)

时间:2020-04-22 20:12:35      阅读:100      评论:0      收藏:0      [点我收藏+]

标签:out   如何   void   pen   前缀   lse   span   stdin   pread   

P4192 旅行规划(分块+凸包)

题目大意

维护序列并支持两种操作

  • 区间加
  • 区间查询最大前缀和(指 \(\sum_{i=1}^xa_i\)

数据范围

对于 $ 100 % $ 的数据,$ n,m \le 100000。$

解题思路

分块经典题

考虑直接对序列求前缀和,那么相当于区间加等差数列,这是很难合并的,不适合使用线段树,所以考虑分块,因为一个等差数列加上另一个等差还是等差数列,所以每块维护两个 tag (首项,公差),代表此块加的等差数列,同时我们可以 $ \Theta (1)$ 求出 $ sum[i]$ 的值,sum[i] = add[x] + d[x] * (i - L[x] + 1) + a[i]

考虑如何查询整块,发现以 i 为 x,sum[i] 为 y 的点 $ (i, sum[i])$ , 每个块做一下凸包,不管加怎样的等差数列,对答案可能有贡献的点一定在凸包上,且凸包进行操作后依然满足单峰状态,可以在凸包上三分查找

代码如下

const int N = 200500;
int SIZ, BCNT;
int st[505][505], tp[505];
int L[N], R[N], bl[N];
ll add[N], a[N], d[N];
int m, n;

void spread(int x) {
	if (!d[x] && !add[x]) return;
	ll tmp = add[x];
	for (int i = L[x]; i <= R[x]; ++i) 
		tmp += d[x], a[i] += tmp;
	d[x] = add[x] = 0;
}

double K(int x, int y) {
	return (double)(a[x] - a[y]) / (x - y);
}

void build(int x) {
	spread(x); int *s = st[x], t = 0;
	for (int i = L[x]; i <= R[x]; i++) {
		while (t > 1 && K(s[t-1], s[t]) < K(s[t-1], i)) --t;
		s[++t] = i;
	}
	tp[x] = t;
}

ll get(int x, int p) {
	return (p - L[x] + 1) * d[x] + add[x] + a[p];
}

ll query(int x) {
	int l = 1, r = tp[x], *s = st[x];
	while (l <= r) {
		int m1 = (l + r) >> 1, m2 = (m1 + r + 1) >> 1;
		if (get(x, s[m1]) > get(x, s[m2])) r = m2 - 1;
		else l = m1 + 1;
	}
	return get(x, s[r]);
}

int main() {
	freopen ("hs.in","r",stdin);
	freopen ("hs.out","w",stdout);
	read(n); SIZ = sqrt(n), BCNT = n / SIZ;
	if (BCNT * SIZ != n) BCNT++;
	for (int i = 1;i <= n; i++) read(a[i]), a[i] += a[i-1];
	for (int i = 1;i <= BCNT; build(i++)) { 
		L[i] = R[i-1] + 1, R[i] = min(R[i-1] + SIZ, n);
		for (int j = L[i];j <= R[i]; j++) bl[j] = i;
	}
	for (read(m); m; m--) {
		ll op, x, y, k;
		read(op), read(x), read(y);
		if (op) {
			ll ans = -1e15;
			spread(bl[x]), spread(bl[y]);
			if (bl[y] - bl[x] <= 1) {
				for (int i = x;i <= y; i++) Mx(ans, get(bl[i], i));
				printf ("%lld\n", ans); continue;
			}
			
			for (int i = x;i <= R[bl[x]]; i++) Mx(ans, get(bl[i], i));
			for (int i = bl[x] + 1; i < bl[y]; i++) Mx(ans, query(i));
			for (int i = L[bl[y]];i <= y; i++) Mx(ans, get(bl[i], i));
			printf ("%lld\n", ans);
			
		}
		else {
			read(k); ll tmp = 0;
			spread(bl[x]), spread(bl[y]);
			
			if (bl[y] - bl[x] <= 1) {
				for (int i = x;i <= y; i++) tmp += k, a[i] += tmp;
				for (int i = y + 1;i <= R[bl[y]]; i++) a[i] += tmp;
				for (int i = bl[y] + 1;i <= BCNT; i++) add[i] += tmp;
				build(bl[x]), bl[x] != bl[y] && (build(bl[y]), 0);
				continue;
			}
			
			for (int i = x; i <= R[bl[x]] ; i++) tmp += k, a[i] += tmp;
			for (int i = bl[x] + 1; i < bl[y] ; i++) d[i] += k, add[i] += tmp, tmp += SIZ * k;
			for (int i = L[bl[y]]; i <= y ; i++) tmp += k, a[i] += tmp;
			for (int i = y + 1;i <= R[bl[y]]; i++) a[i] += tmp;
			for (int i = bl[y] + 1;i <= BCNT; i++) add[i] += tmp;
			build(bl[x]), build(bl[y]);
		}
	}
	return 0;
}

P4192 旅行规划(分块+凸包)

标签:out   如何   void   pen   前缀   lse   span   stdin   pread   

原文地址:https://www.cnblogs.com/Hs-black/p/12755104.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!