「集训队作业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
要求在平面直角坐标系下维护两个操作:
- 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)。
- 给定一个数 \(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;
}