P4192 旅行规划(分块+凸包)
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;
}