李超线段树
首先来看一道题:[HEOI2013]Segment
可以发现的是,实质上某个 \(x = k\) 处的最大值只有一个,因此我们需要尽可能减少计算不优的线段。
那么对于两条线段 \(a, b(a \ne b)\) 它们左右端点横坐标相同,就只会产生如下四种情况:
-
\(a\) 的两端纵坐标均比 \(b\) 的大,显然 \(a\) 会完全覆盖在 \(b\) 上面,因此 \(b\) 是无意义的。
-
\(b\) 的两端纵坐标均比 \(a\) 大,显然 \(b\) 会完全覆盖在 \(a\) 上面,因此 \(a\) 是无意义的。
-
\(a, b\) 交点在中点右侧:如果 \(a.k > b.k\),那么在中点左侧显然 \(a\) 会被 \(b\) 完全覆盖,那么显然 \(a\) 对左半边区间是不优的:否则 \(a\) 对于右边边区间不是最优的。
-
\(a, b\) 交点在中点左侧:如果 \(a.k > b.k\),那么在中点左侧显然 \(b\) 会被 \(a\) 完全覆盖,那么显然 \(b\) 对左半边区间是不优的:否则 \(b\) 对于右边边区间不是最优的。
从特殊情况出发,每次我们都插入一条 \([1, n]\) 的线段。
现在考虑插入一条线段 \(T\),如果当前区间内不存在线段,显然 \(T\) 是最优的,直接插入然后返回。
如果当前区间存在线段,考虑上面的四种情况。
如果是前两条情况,显然不优的那条线段对全局任意一个位置的贡献没有更优的那条直线优,因此我们只需要保存更优的那条直线即可。
如果是后面两种情况,以 \(a.k > b.k\) 两条线段交点在中点左边为例。
显然此时在右半区间 \(b\) 会被 \(a\) 完全覆盖,\(b\) 不会对右边区间的查询有任何贡献。
但 \(b\) 可能对左半边区间的查询有贡献,因此我们将 \(b\) 传入左半边区间进行递归操作。
可以发现,每条线段只保留在了其可能对整个区间造成贡献的最上层区间,因此每个查询时最优的那些线段一定会被保留下来。
那么查询的时候就要一路将经过的所有节点上的最优线段取 \(\max\),类似于标记永久化。
你会发现每次我们修改最多会往半边区间走,每次查询同样也是一样,因此这个复杂度是 \(O(n \log n)\) 的。
回到原题,原题当中要求每次对一个区间插入一条线段。
不难发现我们可以直接使用线段树来维护这个东西,因为线段树每次涉及修改的区间有 \(\log n\) 个,因此总复杂度是 \(O(n \log ^ 2 n)\) 的。
#include <bits/stdc++.h>
using namespace std;
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 100000 + 5;
const int P = 39989;
const int PP = 1e9;
const double eps = 1e-6;
struct tree { double k, b; int id;} t[N << 2];
int n, k, x, y, xx, yy, ans, tot, opt;
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
double F(tree a, double x) { return a.k * x + a.b;}
tree chkmax(tree a, tree b, int x) {
if(fabs(F(a, x) - F(b, x)) < eps) return a.id < b.id ? a : b;
else return F(a, x) < F(b, x) ? b : a;
}
void down(int p, int l, int r, tree k) {
if(!t[p].id) t[p] = k;
else if(F(t[p], l) < F(k, l) && F(t[p], r) < F(k, r)) t[p] = k;
else if(F(t[p], l) >= F(k, l) && F(t[p], r) >= F(k, r)) return;
else {
if(l == r) return;
double Mid = 1.0 * (l + r) / 2;
if(k.k > t[p].k) {
if(F(k, Mid) > F(t[p], Mid)) down(ls, l, mid, t[p]), t[p] = k;
else down(rs, mid + 1, r, k);
}
else {
if(F(k, Mid) > F(t[p], Mid)) down(rs, mid + 1, r, t[p]), t[p] = k;
else down(ls, l, mid, k);
}
}
}
void update(int p, int l, int r, int x, int y, tree k) {
if(l >= x && r <= y) { down(p, l, r, k); return;}
if(mid >= x) update(ls, l, mid, x, y, k);
if(mid < y) update(rs, mid + 1, r, x, y, k);
}
tree query(int p, int l, int r, int x, int y) {
tree ans = t[p];
if(l == r) return ans;
if(mid >= x) ans = chkmax(ans, query(ls, l, mid, x, y), x);
if(mid < y) ans = chkmax(ans, query(rs, mid + 1, r, x, y), x);
return ans;
}
int main() {
n = read();
while (n--) {
opt = read();
if(opt == 0) {
k = read(), k = (k + ans - 1) % P + 1;
printf("%d\n", ans = query(1, 1, P, k, k).id);
}
else {
x = read(), y = read(), xx = read(), yy = read();
x = (x + ans - 1) % P + 1, xx = (xx + ans - 1) % P + 1;
y = (y + ans - 1) % PP + 1, yy = (yy + ans - 1) % PP + 1;
if(x > xx) swap(x, xx), swap(y, yy);
if(x == xx) update(1, 1, P, x, xx, (tree){0, max(y, yy), ++tot});
else {
double K = 1.0 * (yy - y) / (xx - x), B = y - K * x;
update(1, 1, P, x, xx, (tree){K, B, ++tot});
}
}
}
return 0;
}
值得一提的是,这种涉及区间修改以及查询(在某个值域区间范围内也是一样)的问题,往往可以先考虑所有操作都是整个区间的情况,而对区间进行操作往往可以使用线段树多 \(1 \log\) 解决。