两个月前写的李超树博客,现在回看发现当时完全没有理解李超树本质,而且把李超树维护线段和维护直线的情况混为一谈,不堪入目,于是重写。

前几天知道本博客居然真的有读者,而不是我一个人在自嗨,真的非常感动😭其他读者朋友们也欢迎来找我玩呀,也可以讨论数据结构()

再三衡量就不单开文章了,直接在原文章上面改吧,狈宝看到更优质的博客内容也会开心的🥰💕


概述

例题

7月18日原博客内容

久违的新算法,所以请出世界无敌第一可爱的狈音镇楼。

几个月前其实已经听过这个东西了,当时觉得没啥用,而且乍一看有点被唬住了,于是没有学,然后就在几个月后牛客多校的子弹正中眉心。赶紧来补课了。


简单来讲,李超线段树可以动态的维护若干一次函数f(x)=y=kx+b的最值。

与普通的线段树不同,每个节点并不能简单的直接记录一个最值,再去合并,因为随着x的变化,对应的f(x)也会变化。所以我们转而考虑在每个节点存储该节点对应的定义域里能够取到最值的那个函数的参数k和b。由于我们维护的信息并不具有比较平凡的结合律,因此我们更新信息(插入新线段)的时候需要大力分类讨论。这里以最大值为例进行分析:

  1. 若当前区间没有线段,则直接把新线段放入当前区间。
  2. 若当前区间[L,R]有旧线段,且新线段的斜率大于旧线段的斜率,设当前区间中点为mid:
    • 若旧线段在mid的取值大于新线段在mid的取值,则新线段在[L,mid]的取值一定不优,但在[mid+1,R]上的取值可能更优,继续递归更新答案;
    • 若旧线段在mid的取值小于等于新线段在mid的取值,则旧线段在[mid+1,R]的取值一定不优,但在[L,mid]上的取值可能更优,把当前节点的线段替换成新线段,把旧线段下放继续递归更新答案;
  3. 若当前区间[L,R]有旧线段,且新线段的斜率小于旧线段的斜率,设当前区间中点为mid:
    • 若旧线段在mid的取值大于新线段在mid的取值,则新线段在[mid+1,R]的取值一定不优,但在[L,mid]上的取值可能更优,继续递归更新答案;
    • 若旧线段在mid的取值小于等于新线段在mid的取值,则旧线段在[L,mid]的取值一定不优,但在[mid+1,R]上的取值可能更优,把当前节点的线段替换成新线段,把旧线段下放继续递归更新答案;
  4. 若当前区间[L,R]有旧线段,且新线段的斜率等于旧线段的斜率,判断两线段的截距,截距大的取值必然更优,用截距大的替换截距小的。

详细讨论过程比较复杂,但是代码实际写起来并不是很多。这里以2025牛客暑假多校第二场G为例,给出AC代码。代码计算的是最小值。

注意代码由于定义域1e9,所以使用了动态开点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*

This code template was updated by Yukii_P on 2025/4/4.

*/
#include <bits/stdc++.h>
#define FIO cin.tie(0); ios::sync_with_stdio(false)
#define all(x) (x).begin(), (x).end()
#define fi first
#define se second
#define TEST
#define TESTS int t = 1; cin >> t; while (t--)

#if 0
#define int i64
#define inf 0x3f3f3f3f3f3f3f3fLL
#else
#define inf 0x3f3f3f3f
#endif

#ifndef ONLINE_JUDGE
#define debug(...) std::cerr << #__VA_ARGS__ << " = "; dbg1(__VA_ARGS__)
void dbg2() { std::cerr << "\n"; }
template<typename T, typename... T2>
void dbg2(T x, T2... args) {
std::cerr << ", " << x;
dbg2(args...);
}
template<typename T, typename... T2>
void dbg1(T x, T2... args) {
std::cerr << x;
dbg2(args...);
}
#else
#define debug(...)
#endif

using namespace std;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
using pii = std::pair<int, int>;

inline char nc() {
static char buf[1 << 20], *p1, *p2;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++;
}
#ifndef ONLINE_JUDGE
#define nc getchar
#endif
void read() {}
template<typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
x = 0; char c = nc(), up = c;
while(!isdigit(c)) up = c, c = nc();
while(isdigit(c)) x = x * 10 + c - '0', c = nc();
up == '-' ? x = -x : 0;
read(oth...);
}

constexpr i64 N = 3e5 + 10;
constexpr i64 M = 1e18;

#define lc(x) t[x].l
#define rc(x) t[x].r
struct node {
int l, r;
int k;
i64 b = 1e18;
} t[N * 30];
int idx;

i64 calc(int k, i64 b, i64 x) {
return k * x + b;
}

void modify(int& x, i64 l, i64 r, int k, i64 b) {
if (!x) {
x = ++idx;
lc(x) = rc(x) = 0;
t[x].k = k;
t[x].b = b;
return;
}
i64 mid = l + r >> 1;
if (calc(k, b, mid) < calc(t[x].k, t[x].b, mid)) {
swap(k, t[x].k);
swap(b, t[x].b);
}
if (l == r) return;
if (k < t[x].k) modify(rc(x), mid + 1, r, k, b);
else modify(lc(x), l, mid, k, b);
}

i64 query(int x, i64 l, i64 r, i64 v) {
if (!x || l == r) return calc(t[x].k, t[x].b, v);
i64 mid = l + r >> 1;
return min(calc(t[x].k, t[x].b, v), mid < v ? query(rc(x), mid + 1, r, v) : query(lc(x), l, mid, v));
}

void solve() {
idx = 0;
int n, m;
cin >> n >> m;
vector<vector<array<i64, 3>>> e1(n + 1), e2(n + 1);
vector<array<i64, 4>> edges(m);

i64 R = 1e9;

for (auto& [u, v, t, w] : edges) {
cin >> u >> v >> t >> w;
e1[u].push_back({v, t, w});
e2[v].push_back({u, t, w});
R = min(R, t / w);
}

vector<i64> dis1(n + 1, 1e18), dis2(n + 1, 1e18);

auto dijkstra = [&](auto& dis, auto& e, int s) {
priority_queue<pair<i64, int>, vector<pair<i64, int>>, greater<>> q;
q.emplace(0, s);
dis[s] = 0;
while (q.size()) {
auto [d, u] = q.top();
q.pop();
if (d > dis[u]) continue;
for (auto [v, t, w] : e[u]) {
if (dis[v] > d + t) {
dis[v] = d + t;
q.emplace(dis[v], v);
}
}
}
};

dijkstra(dis1, e1, 1);
dijkstra(dis2, e2, n);

int root = 0;

for (auto [u, v, t, w] : edges) {
modify(root, 1, R, -w, t + dis1[u] + dis2[v]);
}

int q;
cin >> q;

vector<int> queries(q);
while (q--) {
i64 k;
cin >> k;
cout << query(root, 1, R, k) << "\n";
}
}

signed main() {

FIO;
TESTS {
solve();
}

return 0;
}

/*

_ _ _____ _ _ _
| \| | ___ _ _ ___ |_ _| _ _ (_) __ __ (_) __ _ | |
| .` | / _ \ | ' \ |___| | | | '_| | | \ V / | | / _` | | |
|_|\_| \___/ |_||_| _____ _|_|_ _|_|_ _|_|_ _\_/_ _|_|_ \__,_| _|_|_
_|"""""|_|"""""|_|"""""|_| |_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'

(Font: ASCII - Train)

"El Psy Kongroo." -- Hououin Kyoma

"私は極色カスタ推しです...だが、狽音ウルシと夜鳴トバリも推す :)" -- Yukii_P

*/

其他例题先挖个坑,等我做一下再补。