loading...qwq

「集训队作业2018」万圣节的积木

本题提交网站。

statement

\(n\) 个二元组 \(l_i, r_i\),保证 \(0\le l_i < r_i \le 10^4\)

定义区间 \(x, y\) 的重心坐标 \(G(x, y)\)\(\dfrac {\sum\limits_{i = x} ^ y \dfrac {l_i + r_i} {2} \cdot (r_i - l_i) } {\sum\limits_{i = x} ^ y r_i -l_i}\)

定义区间 \(x,y\) 是稳定的等价于 \(\forall i \in (x, y], l_i \le G(x, i - 1) \le r_i\)

对于一种划分 \((i_1= 0, i_2], (i_2, i_3], (i_3, i_4], \cdots, (i_{k - 1}, i_k = n]\),其合法等价于 \(\forall j \in [1, k)\),第 \(k - j\) 个区间是稳定的,且后 \(j\) 个区间是稳定的。

一种划分的权值是 \(\min (i_j - i_{j - 1})\),求合法划分的最大权值。

sol

\(k - j\) 个区间是稳定的,且后 \(j\) 个区间是稳定的。

依据稳定的定义「后 \(j\) 个区间是稳定的」则「第 \(k - j\) 个区间是稳定的」。

贪心地,我们尽可能多划分即可。问题瓶颈在快速判断一段前缀是稳定的。

观察到 \(\dfrac {\sum\limits_{i = x} ^ y \dfrac {l_i + r_i} {2} \cdot (r_i - l_i) } {\sum\limits_{i = x} ^ y r_i -l_i} =\) \(\dfrac{1}{2}\cdot\dfrac {\sum\limits_{i = x} ^ y r_i^2 - l_i^2 } {\sum\limits_{i = x} ^ y r_i -l_i}\),记后缀和 \(a_i = \sum\limits_{i\le j} r_j^2 - l_j^2,b_i = \sum\limits_{i\le j} r_j - l_j\)

\(\forall i \in (x, y], l_i \le G(x, i - 1) \le r_i\) \(\Leftrightarrow\) \(\forall i \in (x, y], 2l_i(b_x - b_i) \le a_x - a_i \le 2r_i(b_x - b_i)\) \(\Leftrightarrow\) \(\forall i \in (x, y], -a_x\le - 2l_ib_x + 2l_ib_i -a_i,a_x\le 2r_ib_x - 2r_ib_i + a_i\)

问题变成求一些一次函数在某点的最小值,使用李超线段树即可。

李超线段树

「HEOI2013」Segment

要求在平面直角坐标系下维护两个操作:

  1. 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)
  2. 给定一个数 \(k\),询问与直线 \(x = k\) 相交的线段中,交点纵坐标最大的线段的编号。

使用线段树结构,不好上传信息也比较难 \(O(1)\) 合并标记,但是我们考虑类似标记永久化的思路(实际上标记可能改变位置)。

维护 \([l,r]\)\(mid\) 处的取到最大值的线段即最优线段,实际含义表示这个线段要更新下面整个子树,类似标记永久化一样查询叶子。

如何快速合并标记?首先更新出当前线段树节点的最优线段(如果发生更新,是交换插入线段和最优线段),然后递归下传插入线段可能优于当前最优线段的区间,通过比较在 \(l,r\) 的大小可知向左边还是右边递归,注意到只会递归一侧,于是时间复杂度 \(O(\log n)\)

如果全局插入,时间复杂度 \(O(n\log n)\)。如果区间插入,时间复杂度 \(O(n\log ^2 n)\)

对于全局插入,动态开点后,因为最坏每根直线占用一个节点,空间复杂度 \(O(n)\)

对于区间插入,动态开点后,因为最坏每根直线占用 \(O(\log n)\) 个节点,空间复杂度 \(O(n\log n)\)

当实现李超线段树合并时,加上垃圾回收,空间复杂度和上述一致,对于时间复杂度,因为每根线段只会向下走 \(O(\log n)\) 步,时间复杂度依然和上述一致。「例子

注意到区间插入时,其实常数很小。非常优秀的数据结构。

实现

#include <bits/stdc++.h>
#define ll long long
using namespace std;
constexpr int N = 1e5 + 5;
int n, l[N], r[N];
ll a[N], b[N];
struct line {
    ll k, b;
    line(ll x = 0, ll y = 1e18) {
        k = x, b = y;
    }
    ll operator () (ll x) {
        return k * x + b;
    }
};
struct Seg {
    int rt, tot, ls[N], rs[N];
    line a[N];
    #define mid ((l + r) >> 1)
    void insert(line x, int &p, ll l = 1, ll r = 1e9) { // 全局插入
        if (p == 0) {
            p = ++tot;
        }
        if (x(mid) < a[p](mid)) {
            swap(a[p], x);
        }
        if (l == r) {
            return;
        }
        if (x(l) < a[p](l)) {
            insert(x, ls[p], l, mid);
        }
        if (x(r) < a[p](r)) {
            insert(x, rs[p], mid + 1, r);
        }
    }
    ll ask(ll x, int p, ll l = 1, ll r = 1e9) {
        if (p == 0) {
            return 1e18;
        }
        if (l == r) {
            return a[p](x);
        }
        ll res = a[p](x);
        if (x <= mid) {
            res = min(res, ask(x, ls[p], l, mid));
        } else {
            res = min(res, ask(x, rs[p], mid + 1, r));
        }
        return res;
    }
} seg1, seg2;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
    }
    for (int i = n; i >= 1; i--) {
        a[i] = a[i + 1] + 1ll * r[i] * r[i] - 1ll * l[i] * l[i];
        b[i] = b[i + 1] + r[i] - l[i];
    }
    int ans = 0, last = n + 1;
    for (int i = n; i >= 1; i--) {
        seg1.insert(line(-2ll * l[i], +2 * b[i] * l[i] - a[i]), seg1.rt);
        seg2.insert(line(+2ll * r[i], -2 * b[i] * r[i] + a[i]), seg2.rt);
        if (-a[i] <= seg1.ask(b[i], seg1.rt) && a[i] <= seg2.ask(b[i], seg2.rt)) {
            ans = max(ans, last - i);
            last = i;
        }
    }
    cout << ans << "\n";
    return 0;
}

posted @ 2023-08-06 19:29  olriutre  阅读(22)  评论(0)    收藏  举报