(线段)树学是怎样炼成的
本文所讲的线段树题,仅包括思路重点在线段树的题,而不是用线段树优化什么。
线段树只是一种武器,而扫描线是思想。只有同时有了武器和思想,才能有最大的力量。
因此这里例题被分哪边,实际上取决于重点偏向,作为武器的线段树还是作为思想的扫描线,并不指完全不需要用到另一方的。
本文是写给已经学过线段树和扫描线的,相信看完后能对线段树和扫描线有不少新认识。
线段树
标记信息的设计
概述
本节参考:
https://www.luogu.me/article/rqmfqvmu
https://www.cnblogs.com/Meatherm/p/17925813.html#:~
线段树基本原则(信息&标记):
- 信息+信息->信息(区间合并pushup)
- 标记+标记->标记(tag合并)
- 标记+信息->信息(tag可对信息作用pushdown)
可以从维护信息出发,推出其他信息和标记。
例:小白
- 单点加
- 区间查最大子段和
由于查询,显然信息要维护最大子段和,于是考虑第一点。
一个大区间显然是由左右小区间合并上了,于是为了合并它,就需要再维护前缀最大值和后缀最大值。
由于是单点修改,并不需要标记。
以上可以写成模板,不同的线段树只有三个函数有差别。
struct Tag { //标记 Tag(){} inline friend Tag operator + (Tag l , Tag r){ //信息合并(相当于pushup) } inline void operator += (Tag t){ (*this) = (*this) + t; }}tag[400005];
struct Seg { //信息 inline friend Seg operator + (Seg ls , Seg rs){ //标记作用标记 } inline void operator += (Tag t){ //标记作用信息 }}tr[400005];
inline void pushdown(int p){ if(){ //判断标记非空 tr[p << 1] += tag[p]; tr[p << 1 | 1] += tag[p]; tag[p << 1] += tag[p]; tag[p << 1 | 1] += tag[p]; tag[p] = Tag(); }}
void init(int p , int l , int r){ if(l == r){ return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ tr[p] += k; tag[p] += k; return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}特殊维护
比如线段树维护矩阵,维护并查集,这边以这道例题为例。
P3300 [SDOI2013] 城市规划
因为是连通性,所以我们直接用线段树维护并查集。这个并查集有 个节点,代表最上面 个节点和最下面 个节点。并查集中再维护一个 ,表示这个连通块是否存在建筑。合并时把两个并查集暴力合并成大小为 的并查集,然后去掉中间的节点,留下 的并查集即可。
没有什么思维含量,喜欢写大魔女的可以尝试实现。相似题目:CF811E、P4121。
#include <cstdio>#include <algorithm>#include <cstring>using namespace std;
int n , m , m2; char s[100005][8];
struct Chm { short fa; bool b; };
int find(Chm dsu[] , short x){ if(dsu[x].fa == x) return x; int f = find(dsu , dsu[x].fa); dsu[f].b |= dsu[x].b; return dsu[x].fa = f;}inline int fa_merge(Chm dsu[] , short x , short y){ short xx = find(dsu , x) , yy = find(dsu , y); if(xx == yy) return 0; dsu[xx].fa = yy; dsu[yy].b |= dsu[xx].b; return 1;}
struct Seg { Chm d[12]; int ans;}tr[400005];
Chm qwq[24]; short tmp[24];inline Seg pushup(Seg ls , Seg rs , int mid){ Seg ret; ret.ans = ls.ans + rs.ans; for(int i = 0;i < m2;i++){ qwq[i] = ls.d[i]; qwq[i + m2] = rs.d[i]; qwq[i + m2].fa += m2; } for(int i = 0;i < m;i++) if(s[mid][i] != '.' && s[mid + 1][i] != '.') if(s[mid][i] != '-' && s[mid + 1][i] != '-'){ bool zbj = qwq[find(qwq , i + m)].b && qwq[find(qwq , i + m2)].b; bool lcj = fa_merge(qwq , i + m , i + m2); ret.ans -= (lcj && zbj); } for(int i = 0;i < m2;i++) ret.d[i].fa = i , ret.d[i].b = 0; for(int i = 0 , _ = (m2 << 1);i < _;i++) tmp[i] = 1145; for(int i = 0;i < m2;i++){ short x = find(qwq , i < m? i : i + m2); ret.d[find(ret.d , i)].b |= qwq[x].b; if(tmp[x] < 1145) fa_merge(ret.d , i , tmp[x]); tmp[x] = i; } return ret;}
void init(int p , int l , int r){ if(l == r){ tr[p].ans = 0; for(int i = 0;i < m;i++){ tr[p].d[i].fa = tr[p].d[i + m].fa = i; tr[p].d[i].b = tr[p].d[i + m].b = 0; if(s[l][i] == 'O') tr[p].d[i].b = tr[p].d[i + m].b = 1; } for(int i = 1;i < m;i++) if(s[l][i - 1] != '.' && s[l][i] != '.') if(s[l][i - 1] != '|' && s[l][i] != '|') fa_merge(tr[p].d , i - 1 , i); for(int i = 0;i < m2;i++) if(tr[p].d[i].fa == i && tr[p].d[i].b) tr[p].ans++; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = pushup(tr[p << 1] , tr[p << 1 | 1] , mid);}
void upd(int p , int l , int r , int x){ if(l > x || r < x) return ; if(l == r){ tr[p].ans = 0; for(int i = 0;i < m;i++){ tr[p].d[i].fa = tr[p].d[i + m].fa = i; tr[p].d[i].b = tr[p].d[i + m].b = 0; if(s[l][i] == 'O') tr[p].d[i].b = tr[p].d[i + m].b = 1; } for(int i = 1;i < m;i++) if(s[l][i - 1] != '.' && s[l][i] != '.') if(s[l][i - 1] != '|' && s[l][i] != '|') fa_merge(tr[p].d , i - 1 , i); for(int i = 0;i < m2;i++) if(tr[p].d[i].fa == i && tr[p].d[i].b) tr[p].ans++; return ; } int mid = (l + r) >> 1; upd(p << 1 , l , mid , x); upd(p << 1 | 1 , mid + 1 , r , x); tr[p] = pushup(tr[p << 1] , tr[p << 1 | 1] , mid);}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return pushup( query(p << 1 , l , mid , x , y) , query(p << 1 | 1 , mid + 1 , r , x , y) , mid );}
int main(void){ scanf("%d%d" , &n , &m); m2 = (m << 1); for(int i = 1;i <= n;i++) scanf("%s" , s[i]); init(1 , 1 , n); int q; scanf("%d" , &q); while(q--){ char op[2]; scanf("%s" , op); if(op[0] == 'C'){ int i , j; scanf("%d%d%s" , &i , &j , op); s[i][j - 1] = op[0]; upd(1 , 1 , n , i); } else if(op[0] == 'Q'){ int l , r; scanf("%d%d" , &l , &r); printf("%d\n" , query(1 , 1 , n , l , r).ans); } }}P7497 四方喝彩 & T490354 ZRZ之以太战线问题
这类问题,可以考虑维护与答案无关的标记来辅助维护答案。
比如第一道例题,维护上锁,并且这个标记是不下传的。
比如 =7 大佬出的第二题,可以多维护一个区间最小值。
这里附上第一题的代码,第二题就不给了,因为没过。这里代码的写法,是符合前面给的模板的。可以学习参考一下,应该比大部分题解来得好写,代码也短。
#include <cstdio>#include <algorithm>#include <vector>using namespace std;
const int MOD = 1e9 + 7;
int n , m , a[200005];int nw;
struct Tag { int ad , mul; inline Tag(int _ = 0 , int __ = 1){ ad = _; mul = __; } inline friend Tag operator + (const Tag x , const Tag y) { return Tag( (1LL * x.ad * y.mul % MOD + y.ad) % MOD , 1LL * x.mul * y.mul % MOD ); } inline void operator += (const Tag y) { (*this) = (*this) + y; }}tg[800005];
struct Seg { int s , s2 , num , lock; //s答案,s2未锁 inline friend Seg operator + (const Seg &ls , const Seg &rs){ Seg ret; ret.s = (ls.s + rs.s) % MOD; ret.s2 = (ls.s2 + rs.s2) % MOD; ret.num = ls.num + rs.num; return ret; } inline void upd(const Tag &x , int le){ if((s += MOD - s2) >= MOD) s -= MOD; s2 = 1LL * s2 * x.mul % MOD; if((s2 += 1LL * num * x.ad % MOD) >= MOD) s2 -= MOD; if((s += s2) >= MOD) s -= MOD; }}tr[800005];
inline void pushdown(int p , int l , int r){ if(tg[p].ad != 0 || tg[p].mul != 1){ int mid = (l + r) >> 1; if(!tr[p << 1].lock){ tg[p << 1] += tg[p]; tr[p << 1].upd(tg[p] , mid - l + 1); } if(!tr[p << 1 | 1].lock){ tg[p << 1 | 1] += tg[p]; tr[p << 1 | 1].upd(tg[p] , r - mid); } tg[p] = Tag(); }}
void init(int p , int l , int r){ if(l == r){ tr[p].s = tr[p].s2 = a[l]; tr[p].num = 1; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x || tr[p].lock) return ; if(l >= x && r <= y){ tg[p] += k; tr[p].upd(k , r - l + 1); return ; } int mid = (l + r) >> 1; pushdown(p , l , r); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); if(!tr[p].lock) tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void chm(int p , int l , int r , int x , int y , int k){ if(l > y || r < x) return ; if(l >= x && r <= y){ if(l != r) pushdown(p , l , r); if(k > 0 && tr[p].lock == 0){ //上锁 tr[p].num = 0 , tr[p].s2 = 0; tr[p].lock++; } else if(k < 0 && tr[p].lock == 1){ //解锁 if(l == r) tr[p].num = 1 , tr[p].s2 = tr[p].s; else tr[p] = tr[p << 1] + tr[p << 1 | 1]; tr[p].lock = 0; } else tr[p].lock += k; return ; } int mid = (l + r) >> 1; pushdown(p , l , r); chm(p << 1 , l , mid , x , y , k); chm(p << 1 | 1 , mid + 1 , r , x , y , k); if(!tr[p].lock) tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p , l , r); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y);}
vector <pair <int , int> > del[200005];
int main(void){ scanf("%d%d" , &n , &m); for(int i = 1;i <= n;i++) scanf("%d" , &a[i]); init(1 , 1 , n); for(int i = 1;i <= m;i++){ for(pair <int , int> k : del[i]) chm(1 , 1 , n , k.first , k.second , -1); int op , l , r , x; scanf("%d%d%d" , &op , &l , &r); if(op <= 3) scanf("%d" , &x); if(op == 1) upd(1 , 1 , n , l , r , Tag(x , 1)); if(op == 2) upd(1 , 1 , n , l , r , Tag(0 , x)); if(op == 3){ chm(1 , 1 , n , l , r , 1); if(i + x + 1 <= m) del[i + x + 1].push_back(make_pair(l , r)); } if(op == 4) printf("%d\n" , query(1 , 1 , n , l , r).s); }}从扫描线到历史版本和
其实这边都是关于历史的。
一般的这种题,可以考虑将 l 做横轴,r 做纵轴,这样就成了矩形查询,区间修改,这玩意一般需要用到历史版本和。
我们很容易可以设计出信息和标记,但是问题就在于如何合并。
有的可能要考虑很多种情况,这里给出一种比较好用的方法:矩阵。
实际上可以把信息标记都放在矩阵上,那么就变成矩阵乘法。写出矩阵后,一般大多项都是固定没用的,于是可以把变的提出来,暴力解出转移,这样便有了标记的转移方式,这还可以尝试合并相同的值,大多数情况下这样跑出来的转移是和按照实际意义推的转移是相同的。
以下是暴力转移的代码(待办:用python写一个功能更完整的):
#include <cstdio>#include <algorithm>#include <cstring>using namespace std;
int n;struct QWQ{ char a[105][105][10]; QWQ(){ memset(a , 0 , sizeof(a)); } }A , B;
inline void mul_max(QWQ x , QWQ y){ //M为负无穷 for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++){ int flag = 0; for(int k = n;k >= 1;k--) if(x.a[i][k][1] != 'M' && y.a[k][j][1] != 'M'){ flag = k; break; } if(!flag) continue; printf("(%d %d)=max(" , i , j); for(int k = 1;k <= n;k++){ if(x.a[i][k][1] == 'M' || y.a[k][j][1] == 'M') continue; printf("%s+%s" , x.a[i][k] + 1 , y.a[k][j] + 1); if(k != flag) putchar(','); } putchar(')'); putchar('\n'); }}
inline void mul_(QWQ x , QWQ y){ for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++){ int flag = 0; for(int k = n;k >= 1;k--) if(x.a[i][k][1] != '0' && y.a[k][j][1] != '0'){ flag = k; break; } if(!flag) continue; printf("ret.k[1] = "); for(int k = 1;k <= n;k++){ if(x.a[i][k][1] == '0' || y.a[k][j][1] == '0') continue; printf(" %s * %s" , x.a[i][k] + 1 , y.a[k][j] + 1); if(k != flag) printf(" +"); } printf(";\n"); }}
int main(void){ scanf("%d" , &n); for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) scanf("%s" , A.a[i][j] + 1); for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) scanf("%s" , B.a[i][j] + 1); mul_(A , B);}然后操作如果有涉及到 max 等的东西,可以考虑使用 矩阵,就是把矩乘的 改成 , 改成 。
不过如果题目的操作过于复杂,其实没必要使用暴力转移找不变,可以直接矩乘,只是常数较大。
NOIP2022比赛
考虑第 个点什么时候能对答案有贡献,显然,当且仅当区间包含 ,且满足 ,其中 表示第 个点左边第一个大于 的位置, 同理。于是这样,每个点都变成一个矩形:。
这样处理完,就有两个二维平面 ,答案就是 矩形内 的和。
扫描线要维护:
- 矩形 赋值。
- 矩形 赋值。
- 求矩形 的值。
???赋值怎么测回。冯sir 说不需要撤回。看:
[3,5,2,6,1,4] -> [3,5,5,6,6,6] [5,2,6,1,4] -> [5,5,6,6,6] [2,6,1,4] -> [2,6,6,6] [6,1,4] -> [6,6,6] [1,4] -> [1,4] [4] -> [4]于是问题变成: 或 区间推平,求 的历史和。
去掉历史和是平凡的,这里信息很容易看出来: 的和, 的和, 的和,历史和(答案),区间长度。以上四个量分别记为 。
直接暴力矩阵转移!!!!!!!!!!!!!!!!!!
另外一个推平同理。
然后这个是把答案加到历史和里的。
然后用上面的暴力程序多乘几次,发现除去 就只有 种可能的值,我们维护它,然后再用暴力程序跑一遍,暴力跑出转移式子,直接复制上去即可。
#include <cstdio>#include <algorithm>#include <vector>#include <stack>using namespace std;typedef unsigned long long ll;
struct Tag { ll k[12]; inline Tag(){ k[0]=k[3]=k[6]=1;k[1]=k[2]=k[4]=k[5]=k[7]=k[8]=k[9]=k[10]=k[11]=0; }
inline Tag(int a0 , int a1 , int a2 , int a3 , int a4 , int a5 , int a6 , int a7 , int a8 , int a9 , int a10 , int a11){k[0]=a0;k[1]=a1;k[2]=a2;k[3]=a3;k[4]=a4;k[5]=a5;k[6]=a6;k[7]=a7;k[8]=a8;k[9]=a9;k[10]=a10;k[11]=a11;}
inline Tag friend operator + (Tag l , Tag r) { Tag ret; ret.k[0] = l.k[0] * r.k[0]; ret.k[1] = l.k[0] * r.k[1] + l.k[1] * r.k[6]; ret.k[2] = l.k[0] * r.k[2] + l.k[1] * r.k[7] + l.k[2]; ret.k[3] = l.k[3] * r.k[3]; ret.k[4] = l.k[3] * r.k[4] + l.k[4] * r.k[6]; ret.k[5] = l.k[3] * r.k[5] + l.k[4] * r.k[7] + l.k[5]; ret.k[6] = l.k[6] * r.k[6]; ret.k[7] = l.k[6] * r.k[7] + l.k[7]; ret.k[8] = l.k[8] * r.k[0] + r.k[8]; ret.k[9] = l.k[9] * r.k[3] + r.k[9]; ret.k[10] = l.k[8] * r.k[1] + l.k[9] * r.k[4] + l.k[10] * r.k[6] + r.k[10]; ret.k[11] = l.k[8] * r.k[2] + l.k[9] * r.k[5] + l.k[10] * r.k[7] + l.k[11] + r.k[11]; return ret; }
inline void operator += (Tag x) { (*this) = (*this) + x; }
inline bool zbj(){ return k[0]!=1||k[3]!=1||k[6]!=1||k[1]||k[2]||k[4]||k[5]||k[7]||k[8]||k[9]||k[10]||k[11]; }}lazy[1000005];
struct Seg { ll x , y , xy , s , le; inline Seg friend operator + (Seg l , Seg r){ return (Seg){ l.x + r.x , l.y + r.y , l.xy + r.xy , l.s + r.s , l.le + r.le }; } inline void operator += (Tag p){ (*this) = (Seg){ x * p.k[0] + le * p.k[8] , y * p.k[3] + le * p.k[9] , x * p.k[1] + y * p.k[4] + xy * p.k[6] + le * p.k[10] , x * p.k[2] + y * p.k[5] + xy * p.k[7] + le * p.k[11] + s , le }; }}tr[1000005];
void init(int p , int l , int r){ tr[p].le = r - l + 1; if(l == r) return ; int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);}
inline void pushdown(int p){ if(lazy[p].zbj()){ tr[p << 1] += lazy[p]; tr[p << 1 | 1] += lazy[p]; lazy[p << 1] += lazy[p]; lazy[p << 1 | 1] += lazy[p]; lazy[p] = Tag(); }}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ tr[p] += k; lazy[p] += k; return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
int T , n , a[250005] , b[250005] , la[250005] , lb[250005];
struct QWQ { int l , r , k , op; };vector <QWQ> chm[250005] , qqq[250005];
ll ans[250005];
int main(void){ scanf("%d%d" , &T , &n); init(1 , 1 , n); stack <pair <int , int> > tmp; for(int i = 1;i <= n;i++){ scanf("%d" , &a[i]); while(!tmp.empty() && tmp.top().first < a[i]) tmp.pop(); if(!tmp.empty()) la[i] = tmp.top().second; tmp.push(make_pair(a[i] , i)); } while(!tmp.empty()) tmp.pop(); for(int i = 1;i <= n;i++){ scanf("%d" , &b[i]); while(!tmp.empty() && tmp.top().first < b[i]) tmp.pop(); if(!tmp.empty()) lb[i] = tmp.top().second; tmp.push(make_pair(b[i] , i)); } for(int i = 1;i <= n;i++){ chm[i].push_back((QWQ){la[i] + 1 , i , a[i] , 0}); chm[i].push_back((QWQ){lb[i] + 1 , i , b[i] , 1}); } scanf("%d" , &T); for(int i = 1 , l , r;i <= T;i++){ scanf("%d%d" , &l , &r); qqq[l - 1].push_back((QWQ){l , r , -1 , i}); qqq[r].push_back((QWQ){l , r , 1 , i}); } for(int i = 1;i <= n;i++){ for(QWQ j : chm[i]){ if(j.op) upd(1 , 1 , n , j.l , j.r , Tag(1 , j.k , 0 , 0 , 0 , 0 , 0 , 0 , 0 , j.k , 0 , 0)); if(!j.op)upd(1 , 1 , n , j.l , j.r , Tag(0 , 0 , 0 , 1 , j.k , 0 , 0 , 0 , j.k , 0 , 0 , 0)); } upd(1 , 1 , n , 1 , n , Tag(1 , 0 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0)); for(QWQ j : qqq[i]) ans[j.op] += j.k * query(1 , 1 , n , j.l , j.r).s; } for(int i = 1;i <= T;i++) printf("%llu\n" , ans[i]);}P3246 序列
求区间内所有子区间的最小值的和
我发现类似这种子区间的最值可以按如下思路思考:
比如有 ,那么:
5 2 4 1 3 5 2 2 1 1 2 4 1 3 2 2 1 1 4 1 3 -----> 4 1 1 1 3 1 1 3 3可以发现是一个矩形,扫描线,且不需要撤回,后边的矩形会直接覆盖前面的。
然后就可以考虑每个点在对什么区间是有贡献的。
设 表示前一个比 小的位置,不存在时值为 ; 表示后一个比 小的位置,不存在时值为 。这个可以用单调栈轻松计算
因此值为 的矩形就是 ,由于新的会直接覆盖旧的的,所以 nxt 不需要考虑。这个可以用扫描线离线做。
于是问题变成:
a区间推平,求历史区间的和
历史和只会用矩阵。信息很容易知道,就是区间和 ,区间历史和 ,于是:
用暴力矩乘得出只有四个位置是有值的。直接再跑一次得转移即可。
#include <cstdio>#include <algorithm>#include <stack>#include <vector>using namespace std;typedef long long ll;
struct Tag { ll k1 , k2 , k3 , k4; inline Tag(){k1 = 1; k2 = k3 = k4 = 0; } inline Tag(int _ , int __ , int ___ , int ____): k1(_) , k2(__) , k3(___) , k4(____) {} inline friend Tag operator + (Tag l , Tag r){ Tag ret; ret.k1 = l.k1 * r.k1; ret.k2 = l.k1 * r.k2 + l.k2; ret.k3 = l.k3 * r.k1 + r.k3; ret.k4 = l.k3 * r.k2 + l.k4 + r.k4; return ret; } inline void operator += (Tag x){ (*this) = (*this) + x; }}lz[400005];
struct Seg { ll s , b; int le; inline Seg(){ s = b = le = 0; } inline friend Seg operator + (Seg l , Seg r){ Seg ret; ret.s = l.s + r.s; ret.b = l.b + r.b; ret.le = l.le + r.le; return ret; } inline void operator += (Tag x){ b += x.k2 * s + x.k4 * le; s = x.k1 * s + x.k3 * le; }}tr[400005];
inline void upd(int p , Tag x){ tr[p] += x; lz[p] += x; }
inline void pushdown(int p){ if(lz[p].k1 == 1 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ; upd(p << 1 , lz[p]); upd(p << 1 | 1 , lz[p]); lz[p] = (Tag){1 , 0 , 0 , 0};}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y) { upd(p , k); return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
void init(int p , int l , int r){ if(l == r){ tr[p].s = tr[p].b = 0; tr[p].le = 1; return ;} int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
int n , q , a[100005] , pre[100005];
struct QWQ { int l , r , k , op; };vector <QWQ> uuu[200005] , qqq[100005];
ll ans[100005];
void solve(){ init(1 , 1 , n); stack <pair <int , int> > tmp; for(int i = 1;i <= n;i++){ while(!tmp.empty() && tmp.top().first > a[i]) tmp.pop(); if(!tmp.empty()) pre[i] = tmp.top().second; tmp.push({a[i] , i}); } for(int i = 1;i <= n;i++) uuu[i].push_back((QWQ){pre[i] + 1 , i , a[i] , 0}); for(int i = 1;i <= q;i++){ int l , r; scanf("%d%d" , &l , &r); qqq[l - 1].push_back((QWQ){l , r , -1 , i}); qqq[r].push_back((QWQ){l , r , 1 , i}); } for(int i = 1;i <= n;i++){ for(QWQ j : uuu[i]) upd(1 , 1 , n , j.l , j.r , Tag(0 , 0 , j.k , 0)); upd(1 , Tag(1 , 1 , 0 , 0)); for(QWQ j : qqq[i]) ans[j.op] += 1LL * j.k * query(1 , 1 , n , j.l , j.r).b; } for(int i = 1;i <= q;i++) printf("%lld\n" , ans[i]);}
int main(void){ scanf("%d%d" , &n , &q); for(int i = 1;i <= n;i++) scanf("%d" , &a[i]); solve();}CF997E & 猫猫贴贴
给定一个排列,多次询问,求区间内子区间有多少是取值连续的
毒瘤题,十年河东十年河西,莫欺少年穷。
首先排列很重要,意味着取值不重复,于是取值连续的区间需要满足 ,即。
按照扫描线的思路,放到二维平面上,横 l,纵 r,先把每个点权值改为 ,这个是简单的,可以转换成区间加。
然后考虑更新 和 ,如果可以,问题就变成矩形求 的个数,因为 ,所以这玩意可以用最小值和最小值的个数维护。
#include <cstdio>#include <vector>#include <algorithm>#include <stack>using namespace std;
struct Tag { int ad , tg; inline Tag(){ ad = tg = 0; } inline Tag(int _ , int __): ad(_) , tg(__) {} inline void operator += (Tag b){ ad += b.ad; tg += b.tg; }}lz[480005];
struct Seg { int mi , cntmi; long long ans; inline friend Seg operator + (Seg ls , Seg rs){ Seg ret; if(ls.mi < rs.mi){ ret.mi = ls.mi; ret.cntmi = ls.cntmi; } else if(ls.mi > rs.mi){ ret.mi = rs.mi; ret.cntmi = rs.cntmi; } else{ ret.mi = ls.mi; ret.cntmi = ls.cntmi + rs.cntmi; } ret.ans = ls.ans + rs.ans; return ret; }}tr[480005];
inline void upd(int p , Tag x , int tmp){ lz[p].ad += x.ad; if((tr[p].mi += x.ad) == tmp){ tr[p].ans += 1LL * x.tg * tr[p].cntmi; lz[p].tg += x.tg; }}
inline void pushdown(int p){ if(lz[p].ad == 0 && lz[p].tg == 0) return ; upd(p << 1 , lz[p] , tr[p].mi); upd(p << 1 | 1 , lz[p] , tr[p].mi); lz[p] = Tag(0 , 0);}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ upd(p , k , 0); return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1, l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
void init(int p , int l , int r){ if(l == r){ tr[p].cntmi = 1; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
int n , m , p[120005];int lx[120005] , rx[120005] , li[120005] , ri[120005];
struct QWQ { int l , r , k , id; };vector <QWQ> u[120005] , q[120005];
inline void solve(int retl[120005] , int retr[120005] , bool (*cmp)(int , int)){ stack <pair <int , int> > st; for(int i = 1;i <= n;i++){ while(!st.empty() && cmp(st.top().first , p[i])) st.pop(); retl[i] = st.empty()? 0 : st.top().second; st.push({p[i] , i}); } while(!st.empty()) st.pop(); for(int i = n;i >= 1;i--){ while(!st.empty() && cmp(st.top().first , p[i])) st.pop(); retr[i] = st.empty()? n + 1 : st.top().second; st.push({p[i] , i}); }}
long long ans[120005];
int main(void){ scanf("%d" , &n); for(int i = 1;i <= n;i++) scanf("%d" , &p[i]); scanf("%d" , &m); for(int i = 1;i <= m;i++){ int l , r; scanf("%d%d" , &l , &r); q[l - 1].push_back((QWQ){l , r , -1 , i}); q[r].push_back((QWQ){l , r , 1 , i}); } solve(lx , rx , [](int x , int y) -> bool {return x < y; }); solve(li , ri , [](int x , int y) -> bool {return x > y; }); for(int i = 1;i <= n;i++){ u[lx[i] + 1].push_back((QWQ){i , rx[i] - 1 , p[i] , 0}); u[i + 1].push_back((QWQ){i , rx[i] - 1 , -p[i] , 0}); u[li[i] + 1].push_back((QWQ){i , ri[i] - 1 , -p[i] , 0}); u[i + 1].push_back((QWQ){i , ri[i] - 1 , p[i] , 0}); } for(int i = 1;i <= n;i++){ u[i].push_back((QWQ){1 , n , 1 , 0}); u[1].push_back((QWQ){i , i , -i , 0}); } init(1 , 1 , n); for(int i = 1;i <= n;i++){ for(QWQ j : u[i]) upd(1 , 1 , n , j.l , j.r , Tag(j.k , 0)); upd(1 , Tag(0 , 1) , 0); for(QWQ j : q[i]) ans[j.id] += 1LL * j.k * query(1 , 1 , n , j.l , j.r).ans; } for(int i = 1;i <= m;i++) printf("%lld\n" , ans[i]);}P9990 Ynoi Easy Round 2023 TEST_90 & P10822
多次询问区间的所有子区间有多少满足出现数字种类个数是奇数
首先可以转换成区间内所有子区间的数字个数对 2 取模的和。
一般的,区间出现个数可以用 来计算,根据这个我们很容易写出矩形:
发现区间增加 1 实际上就是异或 1,于是需要线段树实现区间异或,求历史区间和。
有点困难,考虑用矩阵代替大脑思考下。
维护的显然就是区间 1 的数量 ,历史区间和 。于是:
又到了暴力转移的时候。
这方法常数有点大。。。。要卡常(最好不用vector)
#include <cstdio>#include <algorithm>using namespace std;
char buf[1 << 21] , *p1 , *p2;#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++inline int read() { int x = 0; char ch = GC; while(ch > '9' || ch < '0') ch = GC; while(ch >= '0' && ch <= '9'){ x = (x << 1) + (x << 3) + (ch ^ 48); ch = GC; } return x;}
struct Tag { int k1 , k3 , k2 , k4; inline Tag(){ k1 = 1; k2 = k3 = k4 = 0; } inline Tag(int _ , int __ , int ___ , int ____): k1(_) , k2(__) , k3(___) , k4(____) {} inline friend Tag operator + (Tag l , Tag r){ return Tag( l.k1 * r.k1 , l.k1 * r.k2 + l.k2 , l.k3 * r.k1 + r.k3 , l.k3 * r.k2 + l.k4 + r.k4 ); } inline void operator += (Tag x){ (*this) = (*this) + x; }}lz[4000005];
struct Seg { int a , le; long long b; inline Seg(){ a = b = le = 0; } inline Seg(int _ , long long __ , int ___): a(_) , b(__) , le(___) {} inline friend Seg operator + (Seg ls , Seg rs){ return Seg(ls.a + rs.a , ls.b + rs.b , ls.le + rs.le); } inline void operator += (Tag x){ (*this) = Seg(x.k1 * a + x.k3 * le , 1LL * x.k2 * a + b + 1LL * x.k4 * le , le); }}tr[4000004];
inline void pushdown(int p){ if(lz[p].k1 == 1 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ; tr[p << 1] += lz[p]; lz[p << 1] += lz[p]; tr[p << 1 | 1] += lz[p]; lz[p << 1 | 1] += lz[p]; lz[p] = Tag();}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ tr[p] += k; lz[p] += k; return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
void init(int p , int l , int r){ // if(l == r){ tr[p].le = 1; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
int n , m , pre[1000005];struct QWQ { int x , l , r , k , id; inline bool operator < (QWQ b) { if(x == b.x) return id < b.id; return x < b.x; }}qqq[4000005]; int qn;long long ans[1000005];
int main(void){ n = read(); for(int i = 1 , a;i <= n;i++){ a = read(); qqq[i] = (QWQ){pre[a] + 1 , i , n , 0 , 0}; qqq[i + n] = (QWQ){i + 1 , i , n , 0 , 0}; pre[a] = i; } m = read(); qn = (n << 1); for(int i = 1;i <= m;i++){ int l , r; l = read(); r = read(); qqq[++qn] = (QWQ){l - 1 , l , r , 0 , i}; qqq[++qn] = (QWQ){r , l , r , 1 , i}; } sort(qqq + 1 , qqq + 1 + qn); init(1 , 1 , n); int i = 1; while(i <= qn){ int st = qqq[i].x; while(i <= qn && qqq[i].x == st && qqq[i].id == 0) upd(1 , 1 , n , qqq[i].l , qqq[i].r , Tag(-1 , 0 , 1 , 0)) , i++; tr[1] += Tag(1 , 1 , 0 , 0); lz[1] += Tag(1 , 1 , 0 , 0); while(i <= qn && qqq[i].x == st) ans[qqq[i].id] += qqq[i].k? query(1 , 1 , n , qqq[i].l , qqq[i].r).b : -query(1 , 1 , n , qqq[i].l , qqq[i].r).b , i++; } for(int i = 1;i <= m;i++) printf("%lld\n" , ans[i]);}区间最值操作与历史最值问题
本节参考:
论文《区间最值操作与历史最值问题》——吉如一
oi-wki
区间最值操作
实现区间取最值的一种方法。(下以取最小值为例)
区间维护区间最大值(mx),严格次大值(mx2),最大值的个数(cnt)。
然后区间操作()有如下情况:
- :直接退出
- :仅修改最大值,打标记。
- :递归下去。
论文证明复杂度为 。关于复杂度,还可以看看 Segment tree beats 的时间复杂度下限为 2log。
然后如果加上区间加的操作,实现类似,复杂度变为 。
深挖上面那种方法的本质,实际上就是分类。
将点分成两类:最大值和非最大值。
于是另一种实现区间取最值的方法呼吁而出。
仅修改最大值,那么就是推平操作,可以转换成加操作。
标记需要两套:最大的加标记,非最大值的加标记。
于是这种方法便实现了:区间取最值->区间加
这种思路很有启发性。
最佳女选手
区间加,区间取最大值,区间取最小值,求区间和,求区间最大最小
类比:如果同时有取最大和取最小的操作,那么可以分三类。最大,最小,其他。
按照上面方法维护即可,注意合并函数里的细节。
#include <cstdio>#include <algorithm>using namespace std;typedef long long ll;
const ll inf = 1e17;
char buf[1 << 21] , *p1 , *p2;#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++inline int read() { int x = 0; bool f = false; char ch = GC; while(ch > '9' || ch < '0'){ if(ch == '-') f = true; ch = GC; } while(ch >= '0' && ch <= '9'){ x = (x << 1) + (x << 3) + (ch ^ 48); ch = GC; } return f? -x : x;}
struct Tag { ll admx , admi , ad; inline void operator += (Tag b){ admx += b.admx; admi += b.admi; ad += b.ad; }}tg[2000005];
struct Seg { ll mx , mx2; int cntmx; ll mi , mi2; int cntmi; ll sum; int le;
inline friend Seg operator + (Seg ls , Seg rs){ Seg ret; if(ls.mx > rs.mx){ ret.mx = ls.mx; ret.cntmx = ls.cntmx; ret.mx2 = max(ls.mx2 , rs.mx); } else if(ls.mx < rs.mx){ ret.mx = rs.mx; ret.cntmx = rs.cntmx; ret.mx2 = max(ls.mx , rs.mx2); } else{ ret.mx = ls.mx; ret.cntmx = ls.cntmx + rs.cntmx; ret.mx2 = max(ls.mx2 , rs.mx2); } if(ls.mi < rs.mi){ ret.mi = ls.mi; ret.cntmi = ls.cntmi; ret.mi2 = min(ls.mi2 , rs.mi); } else if(ls.mi > rs.mi){ ret.mi = rs.mi; ret.cntmi = rs.cntmi; ret.mi2 = min(ls.mi , rs.mi2); } else { ret.mi = ls.mi; ret.cntmi = ls.cntmi + rs.cntmi; ret.mi2 = min(ls.mi2 , rs.mi2); } ret.sum = ls.sum + rs.sum; ret.le = ls.le + rs.le; return ret; }
inline void operator += (Tag x){ if(mx == mi) sum += x.admx * cntmx; else sum += x.admx * cntmx + x.admi * cntmi + x.ad * (le - cntmx - cntmi); if(mi2 == mx) mi2 += x.admx; else if(mi2 != inf) mi2 += x.ad; if(mx2 == mi) mx2 += x.admi; else if(mx2 != -inf) mx2 += x.ad; mx += x.admx; mi += x.admi; }}tr[2000005];
inline void upd(int p , Tag x){ if(tr[p].mx == tr[p].mi){ if(x.admi == x.ad) x.admi = x.admx; else x.admx = x.admi; } tr[p] += x; tg[p] += x;}
inline void pushdown(int p){ if(tg[p].ad == 0 && tg[p].admx == 0 && tg[p].admi == 0) return ; ll mx = max(tr[p << 1].mx , tr[p << 1 | 1].mx); ll mi = min(tr[p << 1].mi , tr[p << 1 | 1].mi); upd(p << 1 , (Tag){ tr[p << 1].mx == mx? tg[p].admx : tg[p].ad , tr[p << 1].mi == mi? tg[p].admi : tg[p].ad , tg[p].ad }); upd(p << 1 | 1 , (Tag){ tr[p << 1 | 1].mx == mx? tg[p].admx : tg[p].ad , tr[p << 1 | 1].mi == mi? tg[p].admi : tg[p].ad , tg[p].ad }); tg[p] = (Tag){0 , 0 , 0};}
int a[500005];void init(int p , int l , int r){ if(l == r){ tr[p].mx2 = -inf; tr[p].mi2 = inf; tr[p].sum = tr[p].mx = tr[p].mi = a[l]; tr[p].cntmx = tr[p].cntmi = 1; tr[p].le = 1; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd1(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ upd(p , k); return ; } pushdown(p); int mid = (l + r) >> 1; upd1(p << 1 , l , mid , x , y , k); upd1(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd_max(int p , int l , int r , int x , int y , int k){ if(l > y || r < x || tr[p].mi >= k) return ; if(l >= x && r <= y && k < tr[p].mi2){ upd(p , (Tag){0 , k - tr[p].mi , 0}); return ; } pushdown(p); int mid = (l + r) >> 1; upd_max(p << 1 , l , mid , x , y , k); upd_max(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd_min(int p , int l , int r , int x , int y , int k){ if(l > y || r < x || tr[p].mx <= k) return ; if(l >= x && r <= y && k > tr[p].mx2){ upd(p , (Tag){k - tr[p].mx , 0 , 0}); return ; } pushdown(p); int mid = (l + r) >> 1; upd_min(p << 1 , l , mid , x , y , k); upd_min(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; pushdown(p); int mid = (l + r) >> 1; if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid ) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
int n , m;
int main(void){ n = read(); for(int i = 1;i <= n;i++) a[i] = read(); init(1 , 1 , n); m = read(); while(m--){ int op , l , r; op = read(); l = read(); r = read(); if(op == 1){ int x = read(); upd1(1 , 1 , n , l , r , (Tag){x , x , x}); } if(op == 2){ int x = read(); upd_max(1 , 1 , n , l , r , x); } if(op == 3){ int x = read(); upd_min(1 , 1 , n , l , r , x); } if(op == 4) printf("%lld\n" , query(1 , 1 , n , l , r).sum); if(op == 5) printf("%lld\n" , query(1 , 1 , n , l , r).mx); if(op == 6) printf("%lld\n" , query(1 , 1 , n , l , r).mi); }}历史最值问题
前置:复杂标记信息合并。
一般这种可以用 矩阵来设计标记,如果再加上区间最值操作呢?直接看模板把。
线段树3
区间加,区间取 min,求区间和,区间最大值,历史区间最大值
还是按照上面思路分两类:最大,非最大。上面说了可以转换成加法,于是考虑如何维护。
这里还是使用矩阵(由于不会直接推)。
显然信息就是区间最大值 a,历史最大 b(这里历史最值和区间和无关,分别维护)。这样。
让我们暴力拆拆拆。
暴力程序跑出来,显然只需要维护矩阵的第一行两项即可,然后再跑矩阵转移即可。
然后就写完了。
#include <cstdio>#include <algorithm>using namespace std;typedef long long ll;
const int inf = 2e9;
struct Tag { int k1 , k2; //最大值 int k3 , k4; //非最大 inline Tag(){ k1 = k2 = k3 = k4 = 0; } inline Tag(ll _ , ll __ , ll ___ , ll ____): k1(_) , k2(__) , k3(___) , k4(____) {} inline void operator += (Tag x){ (*this) = Tag( k1 + x.k1 , max(k1 + x.k2 , k2) , k3 + x.k3 , max(k3 + x.k4 , k4) ); }}lz[2000005];
struct Seg { int a , b , mx2 , cntmx , le; ll s; inline Seg(){ a = b = mx2 = -inf; cntmx = le = 0; } inline Seg(ll _ , ll __ , ll ___): a(_) , b(__) , s(___) {} inline friend Seg operator + (Seg ls , Seg rs){ Seg ret; ret.b = max(ls.b , rs.b); ret.s = ls.s + rs.s; ret.le = ls.le + rs.le; if(ls.a < rs.a){ ret.a = rs.a; ret.cntmx = rs.cntmx; ret.mx2 = max(ls.a , rs.mx2); } else if(ls.a > rs.a){ ret.a = ls.a; ret.cntmx = ls.cntmx; ret.mx2 = max(ls.mx2 , rs.a); } else{ ret.a = ls.a; ret.cntmx = ls.cntmx + rs.cntmx; ret.mx2 = max(ls.mx2 , rs.mx2); } return ret; } inline void operator += (Tag x){ b = max(b , a + x.k2); a += x.k1; s += 1LL * x.k1 * cntmx + 1LL * x.k3 * (le - cntmx); if(mx2 != -inf){ b = max(b , mx2 + x.k4); mx2 += x.k3; } }}tr[2000005];
inline void upd(int p , Tag x){ tr[p] += x; lz[p] += x; }
inline void pushdown(int p){ if(lz[p].k1 == 0 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ; int mx = max(tr[p << 1].a , tr[p << 1 | 1].a); if(tr[p << 1].a == mx) upd(p << 1 , lz[p]); else upd(p << 1 , Tag(lz[p].k3 , lz[p].k4 , lz[p].k3 , lz[p].k4)); if(tr[p << 1 | 1].a == mx) upd(p << 1 | 1 , lz[p]); else upd(p << 1 | 1 , Tag(lz[p].k3 , lz[p].k4 , lz[p].k3 , lz[p].k4)); lz[p] = Tag(0 , 0 , 0 , 0);}
void add(int p , int l , int r , int x , int y , int k){ if(l > y || r < x) return ; if(l >= x && r <= y){ upd(p , Tag(k , k , k , k)); return ; } int mid = (l + r) >> 1; pushdown(p); add(p << 1 , l , mid , x , y , k); add(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd_min(int p , int l , int r , int x , int y , int k){ if(l > y || r < x || tr[p].a <= k) return ; if(l >= x && r <= y && k > tr[p].mx2){ upd(p , Tag(k - tr[p].a , k - tr[p].a , 0 , 0)); return ; } int mid = (l + r) >> 1; pushdown(p); upd_min(p << 1 , l , mid , x , y , k); upd_min(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid ) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
int a[500005];void init(int p , int l , int r){ if(l == r){ tr[p].a = tr[p].b = tr[p].s = a[l]; tr[p].cntmx = tr[p].le = 1; tr[p].mx2 = -inf; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
int n , m;
int main(void){ scanf("%d%d" , &n , &m); for(int i = 1;i <= n;i++) scanf("%d" , &a[i]); init(1 , 1 , n); while(m--){ int op , l , r; scanf("%d%d%d" , &op , &l , &r); if(op == 1){ int x; scanf("%d" , &x); add(1 , 1 , n , l , r , x); } if(op == 2){ int x; scanf("%d" , &x); upd_min(1 , 1 , n , l , r , x); } if(op == 3) printf("%lld\n" , query(1 , 1 , n , l , r).s); if(op == 4) printf("%d\n" , query(1 , 1 , n , l , r).a); if(op == 5) printf("%d\n" , query(1 , 1 , n , l , r).b); }}CPU监控
这题就可以用 矩阵。
很容易想到维护当前最大值 ,和历史最大值 。
如果再加上推平,就需要再多加一个 。
不难发现只要第一行第一二个和第三行第一二个会变,于是维护这些,然后跑上面的暴力转移代码即可。
#include <cstdio>#include <algorithm>using namespace std;
const int inf = 2147480000;int n , m , a[100005];
inline int Add(int x , int y){ if(x <= -inf || y <= -inf) return -inf; return x + y;}
struct Tag { int k11 , k12 , k21 , k22; inline Tag(){ k11 = 0; k12 = k21 = k22 = -inf; } inline Tag(int _ , int __): k11(_) , k12(_) , k21(__) , k22(__) {} inline friend Tag operator + (Tag l , Tag r){ Tag ret; ret.k11 = Add(l.k11 , r.k11); ret.k12 = max(l.k12 , Add(l.k11 , r.k12)); ret.k21 = max(Add(l.k21 , r.k11) , r.k21); ret.k22 = max(Add(l.k21 , r.k12) , max(l.k22 , r.k22)); return ret; } inline void operator += (Tag x){ (*this) = (*this) + x; }}lazy[400005];
struct Seg { int maxn , ans; inline Seg(){ maxn = ans = -inf; } inline Seg(int _ , int __): maxn(_) , ans(__) {} inline friend Seg operator + (Seg ls , Seg rs){ return Seg( max(ls.maxn , rs.maxn) , max(ls.ans , rs.ans) ); } inline void operator += (Tag rs){ ans = max(ans , max(Add(maxn , rs.k12) , rs.k22)); maxn = max(Add(maxn , rs.k11) , rs.k21); ans = max(ans , maxn); }}tr[400005];
inline void pushdown(int p){ if(lazy[p].k11 != 0 || lazy[p].k12 != -inf || lazy[p].k21 > -inf || lazy[p].k22 > -inf){ tr[p << 1] += lazy[p]; tr[p << 1 | 1] += lazy[p]; lazy[p << 1] += lazy[p]; lazy[p << 1 | 1] += lazy[p]; lazy[p] = Tag(); }}
void init(int p , int l , int r){ if(l == r){ tr[p].maxn = tr[p].ans = a[l]; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
void upd(int p , int l , int r , int x , int y , Tag k){ if(l > y || r < x) return ; if(l >= x && r <= y){ tr[p] += k; lazy[p] += k; return ; } int mid = (l + r) >> 1; pushdown(p); upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) { return tr[p]; } int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
int main(void){ scanf("%d" , &n); for(int i = 1;i <= n;i++) scanf("%d" , &a[i]); init(1 , 1 , n); scanf("%d" , &m); while(m--){ char s[5]; scanf("%s" , s + 1); if(s[1] == 'Q'){ int l , r; scanf("%d%d" , &l , &r); printf("%d\n" , query(1 , 1 , n , l , r).maxn); } if(s[1] == 'A'){ int l , r; scanf("%d%d" , &l , &r); printf("%d\n" , query(1 , 1 , n , l , r).ans); } if(s[1] == 'P'){ int l , r , x; scanf("%d%d%d" , &l , &r , &x); upd(1 , 1 , n , l , r , Tag(x , -inf)); } if(s[1] == 'C'){ int l , r , x; scanf("%d%d%d" , &l , &r , &x); upd(1 , 1 , n , l , r , Tag(-inf , x)); } }}SP1557 GSS2
区间最大子段和,相同数贡献仅算一次。
可以考虑离线,按照 r 排序,然后依次加入。
每个点维护当前加入 i 的和,于是比较显然,加一个点就相当于 的区间加。
显然答案需要历史最大值,于是只需要实现区间加,区间查历史最大值就做完了。
由于 SPOJ 我交不了,就没写代码。
选讲
HDU6315(势能线段树)
, 为 的排列。
- 区间加。
- 求区间 。
注意到答案最大为 ,大约是 1e6,于是维护每个数还要加几次才会更新答案,如果区间最小为 ,则 找到这个点,改答案,总时间复杂度为 。
P5354 Ynoi 由乃的OJ
比较显然,先树剖转链。
然后可以建 棵线段树,然后每颗树维护这段区间传入 出来的值,然后贪心地选 即可。 很可惜过不了。
其实因为运算符一样,可以一起计算:线段树维护传如 出来的值,然后考虑如何合并左右子树。
这样即可:
tr[p][0] = ((tr[p << 1 | 1][0] & (~tr[p << 1][0])) | (tr[p << 1 | 1][1] & tr[p << 1][0]));tr[p][1] = ((tr[p << 1 | 1][0] & (~tr[p << 1][1])) | (tr[p << 1 | 1][1] & tr[p << 1][1]));做完了。
#include <cstdio>#include <algorithm>#include <vector>using namespace std;typedef unsigned long long ll;
int n , m , k; pair <int , ll> a[100005];vector <int> g[100005];
int dep[100005] , son[100005] , fa[100005] , siz[100005];void dfs1(int x , int f , int d){ siz[x] = 1; fa[x] = f; dep[x] = d; int maxn = -1; for(int nxt : g[x]){ if(nxt == f) continue; dfs1(nxt , x , d + 1); siz[x] += siz[nxt]; if(siz[nxt] > maxn){ maxn = siz[nxt]; son[x] = nxt; } }}
int tp[100005] , id[100005] , cnt; pair <int , ll> w[100005];void dfs2(int x , int top){ tp[x] = top; w[id[x] = ++cnt] = a[x]; if(son[x]) dfs2(son[x] , top); for(int nxt : g[x]){ if(nxt == fa[x] || nxt == son[x]) continue; dfs2(nxt , nxt); }}
ll tr[400005][4];
inline void pushup(int p){ tr[p][0] = ((tr[p << 1 | 1][0] & (~tr[p << 1][0])) | (tr[p << 1 | 1][1] & tr[p << 1][0])); tr[p][1] = ((tr[p << 1 | 1][0] & (~tr[p << 1][1])) | (tr[p << 1 | 1][1] & tr[p << 1][1])); tr[p][2] = ((tr[p << 1][2] & (~tr[p << 1 | 1][2])) | (tr[p << 1][3] & tr[p << 1 | 1][2])); tr[p][3] = ((tr[p << 1][2] & (~tr[p << 1 | 1][3])) | (tr[p << 1][3] & tr[p << 1 | 1][3]));}
inline pair <ll , ll> merge(pair <ll , ll> ls , pair <ll , ll> rs , bool t = 0){ pair <ll , ll> ret; if(t) swap(ls , rs); ret.first = ((rs.first & (~ls.first )) | (rs.second & ls.first )); ret.second = ((rs.first & (~ls.second)) | (rs.second & ls.second)); return ret;}
void init(int p , int l , int r){ if(l == r){ tr[p][0] = 0; tr[p][1] = ~(ll)0; if(w[l].first == 1) tr[p][0] &= w[l].second , tr[p][1] &= w[l].second; if(w[l].first == 2) tr[p][0] |= w[l].second , tr[p][1] |= w[l].second; if(w[l].first == 3) tr[p][0] ^= w[l].second , tr[p][1] ^= w[l].second; tr[p][2] = tr[p][0]; tr[p][3] = tr[p][1]; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); pushup(p);}
void upd(int p , int l , int r , int x , pair <int , ll> y){ if(l > x || r < x) return ; if(l == r){ tr[p][0] = 0; tr[p][1] = ~(ll)0; if(y.first == 1) tr[p][0] &= y.second , tr[p][1] &= y.second; if(y.first == 2) tr[p][0] |= y.second , tr[p][1] |= y.second; if(y.first == 3) tr[p][0] ^= y.second , tr[p][1] ^= y.second; tr[p][2] = tr[p][0]; tr[p][3] = tr[p][1]; return ; } int mid = (l + r) >> 1; upd(p << 1 , l , mid , x , y); upd(p << 1 | 1 , mid + 1 , r , x , y); pushup(p);}
pair <ll , ll> query(int p , int l , int r , int x , int y, int k = 0){ if(l >= x && r <= y) return {tr[p][k] , tr[p][1 + k]}; int mid = (l + r) >> 1; if(y <= mid) return query(p << 1 , l , mid , x , y , k); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y , k); return merge( query(p << 1 , l , mid , x , y , k) , query(p << 1 | 1 , mid + 1 , r , x , y , k) , (k > 0) );}
inline pair <ll , ll> getans(int x , int y){ pair <ll , ll> ret1 , ret2 , ret3; bool f1 = 1 , f2 = 1; while(tp[x] != tp[y]){ if(dep[tp[x]] >= dep[tp[y]]){ if(f1) ret1 = query(1 , 1 , n , id[tp[x]] , id[x] , 2) , f1 = 0; else ret1 = merge(ret1 , query(1 , 1 , n , id[tp[x]] , id[x] , 2)); x = fa[tp[x]]; } else{ if(f2) ret2 = query(1 , 1 , n , id[tp[y]] , id[y]) , f2 = 0; else ret2 = merge(query(1 , 1 , n , id[tp[y]] , id[y]) , ret2); y = fa[tp[y]]; } } if(dep[x] < dep[y]) ret3 = query(1 , 1 , n , id[x] , id[y]); else ret3 = query(1 , 1 , n , id[y] , id[x] , 2); if(f1 && f2) return ret3; if(f1) return merge(ret3 , ret2); if(f2) return merge(ret1 , ret3); return merge(merge(ret1 , ret3) , ret2);}
int main(void){ scanf("%d%d%d" , &n , &m , &k); for(int i = 1;i <= n;i++) scanf("%d%llu" , &a[i].first , &a[i].second); for(int i = 1 , u , v;i < n;i++){ scanf("%d%d" , &u , &v); g[u].push_back(v); g[v].push_back(u); } dfs1(1 , 0 , 1); dfs2(1 , 1); init(1 , 1 , n); while(m--){ int op , x , y; ll z; scanf("%d%d%d%llu" , &op , &x , &y , &z); if(op == 1){ pair <ll , ll> ret = getans(x , y); ll ans = 0 , nw = 0; for(int i = k - 1;i >= 0;i--){ bool a0 = (ret.first >> i & 1ull); bool a1 = (ret.second >> i & 1ull); if(a0) ans |= ((ll)1 << i); else if(a1 && (nw | ((ll)1 << i)) <= z){ ans |= ((ll)1 << i); nw |= ((ll)1 << i); } } printf("%llu\n" , ans); } if(op == 2) upd(1 , 1 , n , id[x] , {y , z}); }}P9631
一操作简单区间取最值不说,重点在二操作,有个结论,我不会证,,,就是这种游戏下,如果异或和为 0 则先手必输。
于是可以先维护区间异或和,然后尽量让第二步的人的异或和为 0,这样就是必胜的了。
因为是拿,于是要满足 的才可以拿,算这玩意有几个即可。
#include <cstdio>#include <cstring>#include <algorithm>using namespace std;
int lz[800005];
struct Seg { int mi , cntmi , mi2 , ans; int tot[31]; inline Seg(){ memset(tot , 0 , sizeof(tot)); } inline friend Seg operator + (Seg ls , Seg rs){ Seg ret; ret.ans = (ls.ans ^ rs.ans); if(ls.mi < rs.mi){ ret.mi = ls.mi; ret.cntmi = ls.cntmi; ret.mi2 = min(ls.mi2 , rs.mi); } else if(ls.mi > rs.mi){ ret.mi = rs.mi; ret.cntmi = rs.cntmi; ret.mi2 = min(ls.mi , rs.mi2); } else{ ret.mi = ls.mi; ret.cntmi = ls.cntmi + rs.cntmi; ret.mi2 = min(ls.mi2 , rs.mi2); } for(int i = 0;i <= 30;i++) ret.tot[i] = ls.tot[i] + rs.tot[i]; return ret; } inline void operator += (int x){ if(cntmi & 1) ans ^= mi ^ x; for(int i = 0;i <= 30;i++) if(mi >> i & 1) tot[i] -= cntmi; mi = x; for(int i = 0;i <= 30;i++) if(mi >> i & 1) tot[i] += cntmi; }}tr[800005];
inline void upd(int p , int x){ tr[p] += x; lz[p] = x; }
inline void pushdown(int p){ if(!lz[p]) return ; int mi = min(tr[p << 1].mi , tr[p << 1 | 1].mi); if(tr[p << 1].mi == mi) upd(p << 1 , lz[p]); if(tr[p << 1 | 1].mi == mi) upd(p << 1 | 1 , lz[p]); lz[p] = 0;}
void upd_max(int p , int l , int r , int x , int y , int k){ if(l > y || r < x || tr[p].mi >= k) return ; if(l >= x && r <= y && tr[p].mi2 > k){ upd(p , k); return ; } int mid = (l + r) >> 1; pushdown(p); upd_max(p << 1 , l , mid , x , y , k); upd_max(p << 1 | 1 , mid + 1 , r , x , y , k); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
Seg query(int p , int l , int r , int x , int y){ if(l >= x && r <= y) return tr[p]; int mid = (l + r) >> 1; pushdown(p); if(y <= mid) return query(p << 1 , l , mid , x , y); if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y); return ( query(p << 1 , l , mid , x , y) + query(p << 1 | 1 , mid + 1 , r , x , y) );}
int a[200005];void init(int p , int l , int r){ if(l == r){ tr[p].mi = tr[p].ans = a[l]; tr[p].cntmi = 1; tr[p].mi2 = (1 << 30); for(int i = 0;i <= 30;i++) if(a[l] >> i & 1) tr[p].tot[i]++; return ; } int mid = (l + r) >> 1; init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r); tr[p] = tr[p << 1] + tr[p << 1 | 1];}
int n , q;
int main(void){ scanf("%d%d" , &n , &q); for(int i = 1;i <= n;i++) scanf("%d" , &a[i]); init(1 , 1 , n); while(q--){ int op , l , r , x; scanf("%d%d%d%d" , &op , &l , &r , &x); if(op == 1) upd_max(1 , 1 , n , l , r , x); if(op == 2){ Seg ans = query(1 , 1 , n , l , r); ans.ans ^= x;// printf("w%d\n" , ans.ans); int t = -1; for(int i = 30;i >= 0;i--) if(ans.ans >> i & 1){ t = i; break; } if(t == -1) puts("0"); else printf("%d\n" , ans.tot[t] + (x >> t & 1)); } }}P5524 Ynoi 2012 NOIP2015充满了希望
- 交换
- 推平
- 查询
给你操作,问顺序执行 的操作后所有查询操作的和。
。
我们用线段树记录每个点最后一次执行的二操作的序号。
对于一操作,也是交换。
对于二操作,还是区间推平。
对于三操作,贡献即为 。
那么将询问离线。枚举 ,维护 表示 之间的答案,移动方法如上,需要单点查询,区间加,可以用树状数组。于是做完了。
代码还没写。
扫描线
参考:https://www.luogu.com.cn/article/9vwv8pe7
答案贡献类
可以考虑每个点要区间满足什么要求使得这个点可以对答案做贡献
常用技巧:
lxl《树套堆》
区间取min,撤销第 i 次操作,求单点值。
如题,树套堆。线段树每个节点维护一个堆,然后标记永久化。
。
#include <cstdio>#include <queue>#include <algorithm>using namespace std;
const int inf = 1e9;
struct Zbojin_Queue { priority_queue <int , vector <int> , greater <int> > q , death; inline void push(int x){ q.push(x); } inline void erase(int x){ death.push(x); } inline int top(){ while(!death.empty() && q.top() == death.top()) q.pop() , death.pop(); return q.empty()? inf : q.top(); }}qwq[2000005];
void upd(int p , int l , int r , int x , int y , int k){ if(r < x || l > y) return ; if(l >= x && r <= y){ if(k > 0) qwq[p].push(k); else qwq[p].erase(-k); return ; } int mid = (l + r) >> 1; upd(p << 1 , l , mid , x , y , k); upd(p << 1 | 1 , mid + 1 , r , x , y , k);}
int getmin(int p , int l , int r , int x){ if(l > x || r < x) return inf; int ret = qwq[p].top(); if(l == r) return ret; int mid = (l + r) >> 1; return min(ret , min( getmin(p << 1 , l , mid , x) , getmin(p << 1 | 1 , mid + 1 , r , x) ));}
int main(void){}这个方法也可以用在:
区间推平,区间撤销推平
只需要把值变成 pair,第一个值是操作时间,第二个是推平的值。
然后因为是 pair,所以堆(大根堆)顶的值一定是时间最大的,也就是最近推平的。删除同理。
其实因为插入是按时间顺序的,因此可以用栈来代替。时间复杂度少个 log。
如果用栈,建议用手写的,由于不知名原因,用 STL 的 stack 空间太大了。。。。
例题
CF522D Closest Equals
注意到,显然只有最近的两个相同的点才能对答案做贡献,并且答案区间要包含这两个点,即 ,用上面的树套堆维护扫描线即可。
CF1000F One Occurrence
记 ,为 前一个相同和后一个相同的。假如要点 有贡献,那么区间必须满足 ,扫描线维护即可。
CF453E Little Pony and Lord Tirek
假如 均为 ,可以算出这个点最小需要几时才能到最大值,记为 ,那么容易想到用珂朵莉树维护修改的时间,于是就有多个连续块。
令 为每个块的时间差,然后每个点分两种情况:
- :这个点为 。
- :这个点为 。
可以预先把每个点放到两个二维平面上, 轴代表 , 轴代表 ,坐标则为 ,权值为 或 。
那么每次询问相当于求第一个二维平面 矩形的和,加上第二个二维平面 矩形的和。于是离线下来扫描线,扫描线可以用树状数组比较快。
然后是 时,只需要在第一次修改时暴力处理,由于修改一次后都为 ,所以时间复杂度不会增加。
时间扫描类
常用技巧:时光倒流。
例题
P3863 序列
区间加,求 i 点之前有几个时刻的值大于 y
实现以时间和下标建系,修改是区间加,查询是区间排名,分块。
#include <cstdio>#include <vector>#include <algorithm>using namespace std;typedef long long ll;
int n , m; ll a[100005];
char buf[1 << 21] , *p1 , *p2;#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++inline int read() { int x = 0; bool f = false; char ch = GC; while(ch > '9' || ch < '0'){ if(ch == '-') f = true; ch = GC; } while(ch >= '0' && ch <= '9'){ x = (x << 1) + (x << 3) + (ch ^ 48); ch = GC; } return f? -x : x;}
namespace LOVE { const int kuai = 220; int L[(100005 / kuai) + 5] , R[(100005 / kuai) + 5] , id[100005]; ll b[100005] , lz[(100005 / kuai) + 5];
inline void add(int l , int r , int x){ const int st = id[l] , ed = id[r]; if(st == ed){ for(int i = l;i <= r;i++) a[i] += x; for(int i = L[st];i <= R[st];i++) b[i] = a[i]; sort(b + L[st] , b + R[st] + 1); return ; } for(int i = l;i <= R[st];i++) a[i] += x; for(int i = L[st];i <= R[st];i++) b[i] = a[i]; sort(b + L[st] , b + R[st] + 1); for(int i = st + 1;i < ed;i++) lz[i] += x; for(int i = L[ed];i <= r;i++) a[i] += x; for(int i = L[ed];i <= R[ed];i++) b[i] = a[i]; sort(b + L[ed] , b + R[ed] + 1); }
inline int query(int l , int r , ll x){ const int st = id[l] , ed = id[r]; int ret = 0; if(st == ed){ for(int i = l;i <= r;i++) ret += (a[i] + lz[st] >= x); return ret; } for(int i = l;i <= R[st];i++) ret += (a[i] + lz[st] >= x); for(int i = st + 1;i < ed;i++) ret += (R[i] + b + 1 - lower_bound(b + L[i] , b + R[i] + 1 , x - lz[i])); for(int i = L[ed];i <= r;i++) ret += (a[i] + lz[ed] >= x); return ret; }
inline void init(){ const int N = m / kuai + 1; for(int i = 1;i <= N;i++){ L[i] = R[i - 1] + 1; R[i] = min(R[i - 1] + kuai , m + 1); for(int j = L[i];j <= R[i];j++) id[j] = i; } }}
struct QWQ { int l , r , k , id; };vector <QWQ> u[100005] , q[100005];int ans[100005];
int main(void){ n = read(); m = read(); for(int i = 1 , x , lt = 0;i <= n;i++){ x = read(); u[i].push_back((QWQ){0 , m , x - lt , 0}); lt = x; } LOVE::init(); int nw = 0; for(int i = 1;i <= m;i++){ int op , l , r; op = read(); l = read(); r = read(); if(op == 1){ int x; x = read(); u[l].push_back((QWQ){i , m , x , 0}); u[r + 1].push_back((QWQ){i , m , -x , 0}); } if(op == 2){ q[l].push_back((QWQ){0 , i - 1 , r , ++nw}); } } for(int i = 0;i <= n;i++){ for(QWQ j : u[i]) LOVE::add(j.l + 1 , j.r + 1 , j.k); for(QWQ j : q[i]) ans[j.id] = LOVE::query(j.l + 1 , j.r + 1 , j.k); } for(int i = 1;i <= nw;i++) printf("%d\n" , ans[i]);}常用转换
通过前两节的转换后,查询/修改操作经常可能在二维平面上显示出阶梯状的图像,如三角形、平行四边形。这个是很头疼的。以下有几种解决方法,但是一般都要根据题目特殊设计,所以我们通过例题来讲解
P15133 [ROIR 2026] 最后的滑动窗口问题
1. 无三角
考虑每个数何时可以成为最小值,放到 l r 的平面直角坐标系中,就是一块矩形,这时,题目的查询就是一条斜率为 1 的直线,这是不好的。所以换个坐标系,纵轴是 k,横轴是 l。那么每个数的贡献区域是平行四边形,查询是平行于 x 轴的直线,如图:
首先可以做一次差分:平行四边(粉)= 绿-蓝-红-红蓝重叠部分。

因为这四个部分都有相似的形状,于是只需要求这个形状的东西(红色部分):

为了便于求解,我们再做一次差分,蓝色部分是易求的,问题变成求红+蓝色部分。此时显然又要转换坐标系了,有一个重要的观察:由于查询是平行于 x 轴的直线,所以原坐标系同一行的点在新坐标系中仍连续并在同一行;原坐标系唯一不好处理的是左边的倒三角。综上考虑,我们可以构建出如下新坐标系:

灰色代表原始坐标系合法点的范围,橙色平行四边形是新坐标系。浅绿色区域是要求的部分,深绿区域则是在新坐标系中浅绿的对应部分。不难发现,橙色以外的区域是不合法的,即使加了,也不会影响答案,所以我们利用这些区域,给深绿色部分补全成一个矩形,如图紫色部分所示。
然后扫描线,开两个树状数组(要实现区间加、区间查询),分别维护这两个坐标系 y=i 时,x 轴的值,其中 i 是从下往上枚举的。从图形理解,就是一条线从下往上扫。
2. 直角三角形
这个方法的拆分会涉及到直角三角形。下面我直接一图流,方法和上文是类似的,就当作课后思考题吧!
上面一行是要分平行四边形情况讨论的拆分方法,下一行唯一的一个是无需讨论的。总共有三种拆分方法,右下角我画出了两种直角三角形的处理办法。

致谢&参考文献
论文《区间最值操作与历史最值问题》——吉如一
https://www.luogu.me/article/rqmfqvmu
https://www.cnblogs.com/Meatherm/p/17925813.html#:~
lxl 的课件。
oi-wki 等
感谢 fen sir 对我线段树标题的评论
感谢冯某对我NOIP 2022 比赛解答。
感谢线段树对本文的大力支持。