一类维护前后缀max相关信息的线段树乱编
从楼房重建这一问题说起
题意:n个数,要求单点修改,询问全局前缀最大值个数
- 我们考虑用线段树维护t[p].len,表示该区间前缀最大值个数
- 考虑function::pushup
- 发现左子区间的前缀最大值仍然是整个区间的前缀最大值,于是仅需考虑右子区间的贡献
- 考虑实现函数update(p,l,r,x),表示左面最大值为x时,(p,l,r)这一区间的贡献,容易发现如果整个区间的最大值mx都小于等于x,那么贡献一定为0可以直接返回
- 否则接着考虑左子区间最大值lmx是否>=x,如果小于那么左子区间不作贡献,可以直接return update(rc[p],mid+1,r,x),即右子区间的贡献
- 否则左子区间作出贡献即update(lc[p],l,mid,x),接下来最重要的一步,也是决定这类题可不可以用这种方法做的一步,即能不能快速算出,右子区间在已选定左子区间的情况下的贡献,以下称为key step
- 注意此处不是仅指右子区间的贡献,而是指在选择了左子区间的情况下,右子区间的贡献,因为左子区间会对右子区间照成影响,(比如右子区间某个数仅考虑这个右子区间是前缀最大值,但它比左子区间最大值小,那么就不是整个区间的前缀最大值了)
- 在本题中可以由整个区间的答案-左子区间的答案算出,所以可做,因为整个区间的答案显然等于左子区间的答案+右子区间受左子区间影响下的答案
- 代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*楼房重建*/ #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } const int maxn = 2e5 + 10; struct SegmentTree{ int lc,rc; int len; double mx; }t[maxn<<2]; int cnt = 0; int update(int p,double mx,int l,int r){ if(l == r){ if(t[p].mx > mx) return 1; return 0; } if(t[p].mx < mx) return 0; int mid = (l + r) >> 1; double lmx = t[t[p].lc].mx; if(lmx < mx) return update(t[p].rc,mx,mid+1,r); return update(t[p].lc,mx,l,mid) + t[p].len - t[t[p].lc].len; } void ins(int &p,int l,int r,int pos,double k){ if(!p) p = ++cnt; if(l == r){ t[p].mx = k; t[p].len = 1; return; } int mid = (l + r) >> 1; if(pos <= mid) ins(t[p].lc,l,mid,pos,k); else ins(t[p].rc,mid+1,r,pos,k); t[p].mx = max(t[t[p].lc].mx,t[t[p].rc].mx); t[p].len = t[t[p].lc].len + update(t[p].rc,t[t[p].lc].mx,mid+1,r); } int main(){ int n = read(),m = read(); int rt = 0; for(int i = 1; i <= m; ++i){ int x = read(),y = read(); ins(rt,1,n,x,(double)y/x); printf("%d\n",t[rt].len); } return 0; }
训练:[HNOI2018转盘]
前面的步骤和转化可以看这篇题解,本文仅讲线段树维护的部分
考虑我们要维护的式子$ans$ = $min^{n}_{i = 1}$($max^{2*n}_{j = i}$ $a_{i}$ + i) + $n - 1$
例题是前缀最大值,本题是后缀,只要把左右区间的维护顺序互换以下即可
考虑维护t[p].ans表示,包含了右子区间的最小答案.那么我们发现key step就很显然了,即恰好是t[p].ans,为什么呢,因为定义的时候就已经考虑了右子区间的影响
于是按着上面的方法维护就可以了
完整代码如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*[HNOI/AHOI2018]转盘*/ #include<bits/stdc++.h> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } int n,m,p; const int N = 2e5 + 10; int T[N],a[N]; struct SegmentTree{ int mxa,lc,rc; int ans; }t[N<<2]; int update(int p,int l,int r,int x){/*在右边最值为x的情况下,更新答案*/ if(l == r) return max(t[p].mxa,x) + l; int mid = (l + r) >> 1; if(t[p].mxa <= x) return x + l; int rmx = t[t[p].rc].mxa; if(rmx < x) return min(update(t[p].lc,l,mid,x),mid+1+x); else return min(update(t[p].rc,mid+1,r,x),t[p].ans); } void pushup(int p,int l,int r){ t[p].mxa = max(t[t[p].lc].mxa,t[t[p].rc].mxa); int mid = (l + r) >> 1; t[p].ans = update(t[p].lc,l,mid,t[t[p].rc].mxa); } int cnt = 0; void build(int &p,int l,int r){ if(!p) p = ++cnt; if(l == r){ t[p].mxa = a[l]; return; } int mid = (l + r) >> 1; build(t[p].lc,l,mid); build(t[p].rc,mid+1,r); pushup(p,l,r); } void modify(int p,int l,int r,int pos,int v){ if(l == r){ t[p].mxa = v; return; } int mid = (l + r) >> 1; if(pos <= mid) modify(t[p].lc,l,mid,pos,v); else modify(t[p].rc,mid+1,r,pos,v); pushup(p,l,r); } int ans = 0; void print(){ ans = t[1].ans + n - 1; printf("%d\n",ans); } void readdate(int &x,int &y){ x = read(),y = read(); if(p) x ^= ans,y ^= ans; } int main(){ n = read(),m = read(),p = read(); for(int i = 1; i <= n; ++i) T[i] = read(),a[i] = T[i]; for(int i = 1; i <= n; ++i) a[i+n] = T[i]; for(int i = 1; i <= (n << 1); ++i) a[i] -= i; int rt = 0; build(rt,1,n<<1); print(); while(m--){ int x,y;readdate(x,y); T[x] = y;a[x] = T[x] - x;a[x+n] = T[x] - (x + n); modify(rt,1,n<<1,x,a[x]); modify(rt,1,n<<1,x+n,a[x+n]); print(); } return 0; }