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;
}

原文地址:https://www.cnblogs.com/hankeke/p/bzoj5089.html

时间: 2024-08-05 22:07:15

bzoj5089 最大连续子段和 分块+复杂度分析+凸包的相关文章

【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  之一. 对于 3

最大连续子段和的两种线性算法

问题描述:给一个数组a1,a2,...,an.求这个数组的最大连续子段和.(非空子段) 即,定义Sij=ai+...+aj,则题目要求的是 max{Sij}(1<=i<=j<=n) N^3枚举和优化之后的N^2枚举就不说了,还有NlogN的二分算法也不提,想了解的可以看我的另一篇博客:http://www.cnblogs.com/itlqs/p/5097504.html 这里主要详解两种O(n)的算法. 方法一:动态规划 dp[i]表示以第i位为结尾的最大子段和.那么转移方程就是:dp[

SPOJ 1043 Can you answer these queries I 求任意区间最大连续子段和 线段树

题目链接:点击打开链接 维护区间左起连续的最大和,右起连续的和.. #include <cstdio> #include <iostream> #include <algorithm> #include <string.h> #include <math.h> #include <vector> #include <map> using namespace std; #define N 50050 #define Lson

HDOJ-1003 Max Sum(最大连续子段 动态规划)

http://acm.hdu.edu.cn/showproblem.php?pid=1003 给出一个包含n个数字的序列{a1,a2,..,ai,..,an},-1000<=ai<=1000 求最大连续子段和及其起始位置和终止位置,很基础的动态规划(DP)问题,看完DP第一次做的DP题目 DP真的是一种很优美的算法,或者说思想,但是比较难理解,我对DP的理解还很浅薄 # include <stdio.h> # define INF 1000000000 int main() { i

[题解](线段树最大连续子段和)POJ_3667_Hotel

题意:1.求一个最靠左的长x的区间全部为0,并修改为1,输出这个区间的左端点 2.修改一个区间为0 实际上是维护最大连续子段和,原来也写过 大概需要维护一个左/右最大子段和,当前这段最大子段长,再维护一个lazytag #include<iostream> #include<cstdio> #include<cstring> #define mid (l+r>>1) #define ls x<<1 #define rs x<<1|1

复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度

时间复杂度分析有哪些? 最好情况时间复杂度(best case time complexity) 最坏情况时间复杂度(worst case time complexity) 平均情况时间复杂度(average case time complexity) 均摊时间复杂度(amortized time complexity) 最好.最坏情况时间复杂度 最好情况时间复杂度就是在最理想的情况下,执行这段代码的时间复杂度. 最好情况时间复杂度就是在最糟糕的情况下,执行这段代码的时间复杂度. 来看看下面这段

学好数据结构和算法 —— 复杂度分析

复杂度也称为渐进复杂度,包括渐进时间复杂度和渐进空间复杂度,描述算法随数据规模变化而逐渐变化的趋势.复杂度分析是评估算法好坏的基础理论方法,所以掌握好复杂度分析方法是很有必要的. 时间复杂度 首先,学习数据结构是为了解决“快”和“省”的问题,那么如何去评估算法的速度快和省空间呢?这就需要掌握时间和空间复杂度分析.同一段代码运行在不同环境.不同配置机器.处理不同量级数据…效率肯定不会相同.时间复杂度和空间复杂度是不运行代码,从理论上粗略估计算法执行效率的方法.时间复杂度一般用O来表示,如下例子:计

复杂度分析(下)

复杂度分析(下) 继续上篇,这篇将介绍四个复杂度分析方面的知识点:最好情况时间复杂度.最坏情况时间复杂度.平均情况时间复杂度.均摊时间复杂度. 1.最好.最坏情况时间复杂度 我们以数组查找举例,遍历数组找指定元素,找到则立即返回该元素所在数组的下标位置,没找到则返回-1.代码比较简单我就不写出来了. 我们来分析一下, 最好情况时间复杂度:最好情况当然是数组的第一个元素就是查找的元素,为O(1) 最坏情况时间复杂度:最坏情况就是数组的最后一个元素为查找的元素,需要遍历完整个数组才能找到该元素,或者

算法录 之 复杂度分析。

一个算法的复杂度可以说也就是一个算法的效率,一般来说分为时间复杂度和空间复杂度... 注意接下来说的均是比较YY的,适用与ACM等不需严格分析只需要大致范围的地方,至于严格的算法复杂度分析的那些数学证明,主定理什么的在<算法导论>这本书上有十分详细的讲解,网上应该也会有人写过,这里就不多说了(其实,是我不会而已o(╯□╰)o...). — 到底啥是复杂度呢?先来个栗子. 小明有10个苹果,有一天他饿了,然后准备吃掉一个苹果,但是小明有中二病,他要吃里面重量最大的那个,于是...他需要一个找到那