线段树区间最值、历史最值(吉司机线段树)
区间最值问题可优化的核心在于:区间内数的种类数一定会随着操作不断进行而减少,直到区间内只剩下某一种数,此时就可用懒标记处理批量操作。因此可以考虑用势能分析优化复杂度。
有关线段树势能分析的技巧见这篇博客:blog
无区间加操作时,复杂度为 \(O(n\log n)\)
- 简略证明:对于线段树的所有结点,共包含 \(O(n\log n)\) 个数,故所有结点内包含数的种类数总和也是 \(O(n\log n)\) 的。该算法的瓶颈在于修改值 \(x \le mxse\) 时需要继续暴力递归两个左右儿子,但是每向下暴力 \(dfs\) 一次,一定会导致该结点内的数的种类数至少减 \(1\)(至少最大值一定会收敛到次大值,即与次大值合并)。故结合势能分析,暴力向下 \(dfs\) 的总次数不会超过 \(O(n\log n)\)。
有区间加操作时,需要额外添加懒标记(对最大值与非最大值的懒更新机制要有所区别),复杂度为 \(O(n\log^{2} n)\)
对区间做 \(\min,\max\) 的操作,本质上转化成了对区间最大值的加操作。
有关更多资料见 CF blog
P6242(模板)
吉司机线段树模板题,涉及区间加、区间更新最值(\(x = min(x, y)\))、区间求和、区间求最值、区间求历史最值。
const int N = 500005;
const ll LOWEST = -4e18; // 无穷小,用于表示不存在次大值
int arr[N];
ll sum[N << 2], maxv[N << 2], maxcnt[N << 2], maxv_se[N << 2], maxHistory[N << 2];
ll maxAdd[N << 2], otherAdd[N << 2], maxAddTop[N << 2], otherAddTop[N << 2];
#define lc p<<1
#define rc p<<1|1
void pushup(int p) {
sum[p] = sum[lc] + sum[rc];
maxv[p] = max(maxv[lc], maxv[rc]);
maxHistory[p] = max(maxHistory[lc], maxHistory[rc]);
if (maxv[lc] > maxv[rc]){
maxcnt[p] = maxcnt[lc];
maxv_se[p] = max(maxv_se[lc], maxv[rc]);
}
else if (maxv[lc] < maxv[rc]){
maxcnt[p] = maxcnt[rc];
maxv_se[p] = max(maxv[lc], maxv_se[rc]);
}
else{
maxcnt[p] = maxcnt[lc] + maxcnt[rc];
maxv_se[p] = max(maxv_se[lc], maxv_se[rc]);
}
}
// 修改结点p的信息与懒标记
void lazy(int p, int n, ll maxAddv, ll otherAddv, ll maxUpv, ll otherUpv){ // n 表示结点区间长度
maxHistory[p] = max(maxHistory[p], maxv[p] + maxUpv);
maxAddTop[p] = max(maxAddTop[p], maxAdd[p] + maxUpv);
otherAddTop[p] = max(otherAddTop[p], otherAdd[p] + otherUpv);
sum[p] += maxAddv * maxcnt[p] + otherAddv * (n - maxcnt[p]);
maxv[p] += maxAddv;
if (maxv_se[p] != LOWEST){
maxv_se[p] += otherAddv;
}
maxAdd[p] += maxAddv;
otherAdd[p] += otherAddv;
}
void pushdown(int p, int ln, int rn){ // ln、rn 分别表示左、右子结点区间长度
ll tmp = max(maxv[lc], maxv[rc]); // 注意不能直接用 maxv[p],因为可能已经被修改过
if (maxv[lc] == tmp){
lazy(lc, ln, maxAdd[p], otherAdd[p], maxAddTop[p], otherAddTop[p]);
}
else{
lazy(lc, ln, otherAdd[p], otherAdd[p], otherAddTop[p], otherAddTop[p]);
}
if (maxv[rc] == tmp){
lazy(rc, rn, maxAdd[p], otherAdd[p], maxAddTop[p], otherAddTop[p]);
}
else{
lazy(rc, rn, otherAdd[p], otherAdd[p], otherAddTop[p], otherAddTop[p]);
}
// clear
maxAdd[p] = otherAdd[p] = maxAddTop[p] = otherAddTop[p] = 0;
}
void build(int p, int l, int r){
if (l == r){
sum[p] = maxv[p] = maxHistory[p] = arr[l];
maxv_se[p] = LOWEST;
maxcnt[p] = 1;
}
else{
int mid = l + r >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
maxAdd[p] = otherAdd[p] = maxAddTop[p] = otherAddTop[p] = 0;
}
void add(int p, int nl, int nr, int ql, int qr, ll v){
if (ql <= nl && nr <= qr){
lazy(p, nr - nl + 1, v, v, v, v);
return;
}
int mid = nl + nr >> 1;
pushdown(p, mid - nl + 1, nr - mid);
if (ql <= mid) add(lc, nl, mid, ql, qr, v);
if (qr > mid) add(rc, mid + 1, nr, ql, qr, v);
pushup(p);
}
void setMin(int p, int nl, int nr, int ql, int qr, ll v){
if (v >= maxv[p]) return;
if (ql <= nl && nr <= qr && maxv_se[p] < v){
lazy(p, nr - nl + 1, v - maxv[p], 0, v - maxv[p], 0);
return;
}
int mid = nl + nr >> 1;
pushdown(p, mid - nl + 1, nr - mid);
if (ql <= mid) setMin(lc, nl, mid, ql, qr, v);
if (qr > mid) setMin(rc, mid + 1, nr, ql, qr, v);
pushup(p);
}
ll querySum(int p, int nl, int nr, int ql, int qr){
if (ql <= nl && nr <= qr){
return sum[p];
}
int mid = nl + nr >> 1;
pushdown(p, mid - nl + 1, nr - mid);
ll res = 0;
if (ql <= mid) res += querySum(lc, nl, mid, ql, qr);
if (qr > mid) res += querySum(rc, mid + 1, nr, ql, qr);
return res;
}
ll queryMax(int p, int nl, int nr, int ql, int qr){
if (ql <= nl && nr <= qr){
return maxv[p];
}
int mid = nl + nr >> 1;
pushdown(p, mid - nl + 1, nr - mid);
ll res = LOWEST;
if (ql <= mid) res = max(res, queryMax(lc, nl, mid, ql, qr));
if (qr > mid) res = max(res, queryMax(rc, mid + 1, nr, ql, qr));
return res;
}
ll queryHistoryMax(int p, int nl, int nr, int ql, int qr){
if (ql <= nl && nr <= qr){
return maxHistory[p];
}
int mid = nl + nr >> 1;
pushdown(p, mid - nl + 1, nr - mid);
ll res = LOWEST;
if (ql <= mid) res = max(res, queryHistoryMax(lc, nl, mid, ql, qr));
if (qr > mid) res = max(res, queryHistoryMax(rc, mid + 1, nr, ql, qr));
return res;
}
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> arr[i];
}
build(1, 1, n);
int op, ql, qr;
ll v;
for (int i = 1; i <= m; i++) {
cin >> op >> ql >> qr;
if (op == 1) {
cin >> v;
add(1, 1, n, ql, qr, v);
}
else if (op == 2) {
cin >> v;
setMin(1, 1, n, ql, qr, v);
}
else if (op == 3) {
cout << querySum(1, 1, n, ql, qr) << "\n";
}
else if (op == 4) {
cout << queryMax(1, 1, n, ql, qr) << "\n";
}
else {
cout << queryHistoryMax(1, 1, n, ql, qr) << "\n";
}
}
}
CF1290E
CF997E
2020ICPC南京J
涉及到区间最值修改、查询区间异或和、查询区间内某个二进制位上 \(1\) 的个数。由于吉司机线段树最值修改的本质是只对区间最大/最小值进行修改,可以看作被修改的值只有一种,因此对于绝大多数信息,都可以很方便地维护。具体实现思路见官解。

浙公网安备 33010602011771号