一类维护前后缀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
  • 注意此处不是仅指右子区间的贡献,而是指在选择了左子区间的情况下,右子区间的贡献,因为左子区间会对右子区间照成影响,(比如右子区间某个数仅考虑这个右子区间是前缀最大值,但它比左子区间最大值小,那么就不是整个区间的前缀最大值了)
  • 在本题中可以由整个区间的答案-左子区间的答案算出,所以可做,因为整个区间的答案显然等于左子区间的答案+右子区间受左子区间影响下的答案
  • 代码如下:
/*楼房重建*/ 
#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;
}
View Code

  训练:[HNOI2018转盘]

  前面的步骤和转化可以看这篇题解,本文仅讲线段树维护的部分

  考虑我们要维护的式子$ans$ = $min^{n}_{i = 1}$($max^{2*n}_{j = i}$ $a_{i}$ + i) + $n - 1$

  例题是前缀最大值,本题是后缀,只要把左右区间的维护顺序互换以下即可

  考虑维护t[p].ans表示,包含了右子区间的最小答案.那么我们发现key step就很显然了,即恰好是t[p].ans,为什么呢,因为定义的时候就已经考虑了右子区间的影响

  于是按着上面的方法维护就可以了  

  完整代码如下

  

/*[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;
}
View Code

 

posted @ 2021-02-08 19:37  y_dove  阅读(195)  评论(0编辑  收藏  举报