【数据结构】[ABC196E] Filters 加强版

题目描述

[ABC196E] Filters 加强版 ,欢迎来测试数据。

给定整数序列 A = ( a 1 , a 2 , a 3 , … , a N ) , T = ( t 1 , t 2 , t 3 , … , t N ) A=(a_1,a_2,a_3,\ldots,a_N),T=(t_1,t_2,t_3,\ldots,t_N) A=(a1,a2,a3,,aN),T=(t1,t2,t3,,tN),有 N N N 个函数 f 1 ( x ) , f 2 ( x ) , … , f N ( x ) f_1(x),f_2(x),\ldots,f_N(x) f1(x),f2(x),,fN(x) 表示如下:

f i ( x ) = { x + a i , ( t i = 1 ) min ⁡ ( x , a i ) , ( t i = 2 ) max ⁡ ( x , a i ) , ( t i = 3 ) f_i(x)=\begin{cases} x+a_i,&(t_i=1)\\ \min(x,a_i),&(t_i=2)\\ \max(x,a_i),&(t_i=3) \end{cases} fi(x)= x+ai,min(x,ai),max(x,ai),(ti=1)(ti=2)(ti=3)

现在给定 Q Q Q 次询问,每次询问按照如下的格式之一:

  1. 1 x y,表示将第 x x x 个函数的 ( a x , t x ) (a_x,t_x) (ax,tx) 修改为 ( y , 1 ) (y,1) (y,1)
  2. 2 x y,表示将第 x x x 个函数的 ( a x , t x ) (a_x,t_x) (ax,tx) 修改为 ( y , 2 ) (y,2) (y,2)
  3. 3 x y,表示将第 x x x 个函数的 ( a x , t x ) (a_x,t_x) (ax,tx) 修改为 ( y , 3 ) (y,3) (y,3)
  4. 4 x,表示查询一个数 x x x,输出 f N ( … f 3 ( f 2 ( f 1 ( x ) ) ) ) f_N(\ldots f_3(f_2(f_1(x)))) fN(f3(f2(f1(x))))

题解

假定我们只有 4 4 4 操作,看一下应该怎么做(啊那不就是原题吗)。
我现在很想知道对于每个 x x x,经过处理过后他长什么样,也就是我要知道 f N ( … f 3 ( f 2 ( f 1 ( x ) ) ) ) f_N(\ldots f_3(f_2(f_1(x)))) fN(f3(f2(f1(x)))) 的函数图像,这是很好做的。
我们发现对于每一个 2 2 2 操作,他会把一个函数的上半部分改成一条平的直线 y = a i y=a_i y=ai(如果函数都没够到这里就不用考虑了),那么 3 3 3 操作就是把一个函数的下半部分改成一条平的直线,同理。 1 1 1 操作是把函数整体上移,鉴于我们最初的初始函数是 y = x y=x y=x,那么我们最终的函数一定形如:

函数图像对没错就是这么简单,我们发现这个函数他是有上界和下界的,只要我们知道了上界和下界我们就能求出每个 x x x 的函数值。这很好求,如果遇到了 1 1 1 操作,那么 l , r l,r l,r 都要加上 a i a_i ai,如果是 2 , 3 2,3 2,3 操作,要比较当前函数的上下界。

那么我们已经拿到很大一部分分了,接下来考虑带上 1 , 2 , 3 1,2,3 1,2,3 操作应该怎么做。由于这三个小函数不一定满足交换律,我们直接去更改并用某种我们暂时不知道的数据结构去维护显然有点麻烦,接下来我们以 ( t 1 = 1 , a 1 = 5 ) , ( t 2 = 2 , a 2 = 9 ) (t_1=1,a_1=5),(t_2=2,a_2=9) (t1=1,a1=5),(t2=2,a2=9) 来讲解一下如何化简问题。

首先我们就按顺序做,那么得出来的函数图像应该是:
举个例子我们发现原本加上的 5 5 5 被第二个操作限制住了,这导致我们没法交换这两个函数。但是如果我把第二个函数改成 ( t 2 = 2 , a 1 = 4 ) (t_2=2,a_1=4) (t2=2,a1=4),再交换这两个函数,会发现画出的图像是一样的,我们提前计算了 1 1 1 操作的影响,所以直接减去,修改了 2 , 3 2,3 2,3 操作的值。
换句话说,第 i i i 个操作如果为 1 1 1,那么把 i + 1 i+1 i+1 后面的所有 2 , 3 2,3 2,3 操作都减去 a i a_i ai,就可以把第 i i i 个函数拖到最后面去不管他了。

于是我们得到了我们的算法,如果要把一个函数修改为 1 1 1,必须先消除他对他后面函数的影响,再重新计算他的影响。这涉及到区间加,所以我们考虑带标记线段树,步骤如下:

  1. 检测当前要被操作的是不是 1 1 1 操作,如果是 1 1 1 操作,把 [ i + 1 , n ] [i+1,n] [i+1,n] 的所有影响抵消掉,即加上 a i a_i ai,但对于第 i i i 个操作,我们要减去 a i a_i ai,原因显然,把这个节点的 l , r l,r l,r 更新为 ± ∞ \pm\infty ±
  2. 如果修改后的是 1 1 1 操作,那么对 [ i + 1 , n ] [i+1,n] [i+1,n] 新增一个影响,对 i i i 节点影响同上, l , r l,r l,r 更新同上。
  3. 如果修改后的是 2 , 3 2,3 2,3 操作,那么查询 [ 1 , i − 1 ] [1,i-1] [1,i1] 的影响,将 y y y 减去这个值(因为 y y y 不可能不受这些影响,所以显然是要这么做的),然后判断是否要更新当前节点的 l , r l,r l,r 值。
  4. 4 4 4 操作十分简单,直接查询线段树根的 l , r , Δ l,r,\Delta l,r,Δ 值。

代码函数功能讲解:

  1. change 函数:针对 [ i + 1 , n ] [i+1,n] [i+1,n] 的修改的函数,需要考虑打上标记,但是这个标记是只对 l , r l,r l,r 生效的,不会对 Δ \Delta Δ 生效(显然)。
  2. change_single 函数:其实可以直接用 change 函数替代了,没什么用,专门用来做单点修改,只是我希望复杂度能好一点。
  3. change_min 函数:针对 l , r l,r l,r 修改的单点操作,显然不适用于标记,但是要下传标记(任何函数都要这么做)。
  4. ask 函数:用于检测我被修改的函数是不是 1 1 1 操作,显然这样的操作都具备 Δ ≠ 0 \Delta\neq 0 Δ=0 的特征,所以我们只要查询这个就可以了。
  5. ask_sum 函数:用于在进行 2 , 3 2,3 2,3 操作时,查找 [ 1 , i − 1 ] [1,i-1] [1,i1] 的影响,也就是 ∑ j = 1 i − 1 Δ j \sum\limits_{j=1}^{i-1}\Delta_j j=1i1Δj
  6. pushup 函数,整个线段树的难点所在,显然他的合并并不是直接 max ⁡ { l } , min ⁡ { r } \max\{l\},\min\{r\} max{l},min{r} 这么简单,因为操作还具有时间上的性质(指的是 2 , 3 2,3 2,3 操作),所以我们后面的函数有可能会覆盖前面的函数,而不是一起更新,一起更新会导致后面的操作没有成功覆盖前面的函数,画个图了解一下(不考虑 Δ \Delta Δ):举个例子2 y y y 轴左侧表示 p × 2 p\times 2 p×2 的线段树结点函数,右侧表示 p × 2 + 1 p\times 2+1 p×2+1 的线段树结点函数。显然右侧函数会覆盖左侧函数,这个时候左侧函数是没有用的,废弃掉了,只能从右侧 pushup 上来。

pushuppushdown 的代码(单独拎出来方便查看):

void pushup(int p){
    a[p].l=a[p<<1].l,a[p].r=a[p<<1].r;
    if(a[p<<1].l>a[p<<1].r) a[p<<1].l=a[p<<1].r;
    a[p].l=a[p<<1|1].l,a[p].r=a[p<<1|1].r;
    if(a[p<<1|1].l>a[p<<1|1].r) a[p<<1|1].l=a[p<<1|1].r;
    //以上四行是检测 l>r 的情况,自动统一 l=r
    if(a[p<<1|1].r<a[p<<1].l){
        a[p].l=a[p].r=a[p<<1|1].r;
        a[p].x=a[p<<1].x+a[p<<1|1].x;
        return;
    }
    if(a[p<<1|1].l>a[p<<1].r){
        a[p].l=a[p].r=a[p<<1|1].l;
        a[p].x=a[p<<1].x+a[p<<1|1].x;
        return;
    }
    //这上面两个 if 就是正常合并
    a[p].l=max(a[p<<1].l,a[p<<1|1].l);
    a[p].r=min(a[p<<1].r,a[p<<1|1].r);
    a[p].x=a[p<<1].x+a[p<<1|1].x;
}
void pushdown(int p,int l,int r){
    tag[p<<1]+=tag[p];
    tag[p<<1|1]+=tag[p];
    if(a[p<<1].l!=-inf) a[p<<1].l+=tag[p];
    if(a[p<<1].r!=inf) a[p<<1].r+=tag[p];
    if(a[p<<1|1].l!=-inf) a[p<<1|1].l+=tag[p];
    if(a[p<<1|1].r!=inf) a[p<<1|1].r+=tag[p];
    //一定要正确判断 l 和 r 是否是无穷,无穷的就别更改了
    tag[p]=0;
}

总时间复杂度 O ( q log ⁡ n ) \mathcal{O}(q\log n) O(qlogn),你也可以用分块做,或许会更快,代码应该更短。

代码

#include<bits/stdc++.h>
#define int long long
#define mid ((l+r)>>1)
#define fir first
#define sec second
#define lowbit(i) (i&(-i))
using namespace std;
const int N=5e5+5;
const int inf=1e18;
inline int read(){
    char op=getchar();
    int w=0,s=1;
    while(op<'0'||op>'9'){
        if(op=='-') s=-1;
        op=getchar();
    }
    while(op>='0'&&op<='9'){
        w=(w<<1)+(w<<3)+op-'0';
        op=getchar();
    }
    return w*s;
}
struct segment_tree{int l,r,x;}a[N];
int tag[N];
struct operation{int l,r,x;}f[N];
void pushup(int p){
    a[p].l=a[p<<1].l,a[p].r=a[p<<1].r;
    if(a[p<<1].l>a[p<<1].r) a[p<<1].l=a[p<<1].r;
    a[p].l=a[p<<1|1].l,a[p].r=a[p<<1|1].r;
    if(a[p<<1|1].l>a[p<<1|1].r) a[p<<1|1].l=a[p<<1|1].r;
    if(a[p<<1|1].r<a[p<<1].l){
        a[p].l=a[p].r=a[p<<1|1].r;
        a[p].x=a[p<<1].x+a[p<<1|1].x;
        return;
    }
    if(a[p<<1|1].l>a[p<<1].r){
        a[p].l=a[p].r=a[p<<1|1].l;
        a[p].x=a[p<<1].x+a[p<<1|1].x;
        return;
    }
    a[p].l=max(a[p<<1].l,a[p<<1|1].l);
    a[p].r=min(a[p<<1].r,a[p<<1|1].r);
    a[p].x=a[p<<1].x+a[p<<1|1].x;
}
void pushdown(int p,int l,int r){
    tag[p<<1]+=tag[p];
    tag[p<<1|1]+=tag[p];
    if(a[p<<1].l!=-inf) a[p<<1].l+=tag[p];
    if(a[p<<1].r!=inf) a[p<<1].r+=tag[p];
    if(a[p<<1|1].l!=-inf) a[p<<1|1].l+=tag[p];
    if(a[p<<1|1].r!=inf) a[p<<1|1].r+=tag[p];
    tag[p]=0;
}
void build(int p,int l,int r){
    if(l==r){
        a[p].l=f[l].l;
        a[p].r=f[l].r;
        a[p].x=f[l].x;
        return;
    }
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    pushup(p);
}
int ask(int p,int l,int r,int pos){
    if(l==pos&&r==pos) return a[p].x;
    if(tag[p]) pushdown(p,l,r);
    int ans=0;
    if(pos<=mid) ans=ask(p<<1,l,mid,pos);
    else ans=ask(p<<1|1,mid+1,r,pos);
    pushup(p);
    return ans;
}
void change(int p,int l,int r,int x,int y,int k){
    if(x<=l&&r<=y){
        if(a[p].l!=-inf) a[p].l+=k;
        if(a[p].r!=inf) a[p].r+=k;
        tag[p]+=k;
        return;
    }
    if(tag[p]) pushdown(p,l,r);
    if(x<=mid) change(p<<1,l,mid,x,y,k);
    if(y>mid) change(p<<1|1,mid+1,r,x,y,k);
    pushup(p);
}
void change_min(int p,int l,int r,int pos,int x,int y){
    if(l==pos&&r==pos){
        a[p].l=x;
        a[p].r=y;
        return;
    }
    if(tag[p]) pushdown(p,l,r);
    if(pos<=mid) change_min(p<<1,l,mid,pos,x,y);
    else change_min(p<<1|1,mid+1,r,pos,x,y);
    pushup(p);
}
int ask_sum(int p,int l,int r,int x,int y){
    if(x<=l&&r<=y) return a[p].x;
    if(tag[p]) pushdown(p,l,r);
    int ans=0;
    if(x<=mid) ans+=ask_sum(p<<1,l,mid,x,y);
    if(y>mid) ans+=ask_sum(p<<1|1,mid+1,r,x,y);
    pushup(p);
    return ans;
}
void change_single(int p,int l,int r,int x,int k){
    if(l==x&&r==x){
        a[p].x+=k;
        return;
    }
    if(tag[p]) pushdown(p,l,r);
    if(x<=mid) change_single(p<<1,l,mid,x,k);
    else change_single(p<<1|1,mid+1,r,x,k);
    pushup(p);
}
signed main(){
    int n=read(),del=0;
    for(register int i=1;i<=n;i++){
        int op=read(),x=read();
        if(op==1) del+=x,f[i].l=-inf,f[i].r=inf,f[i].x=x;
        else if(op==2) x-=del,f[i].l=-inf,f[i].r=x;
        else x-=del,f[i].l=x,f[i].r=inf;
    }
    build(1,1,n);
    int q=read();
    while(q--){
        int op=read(),pos,x;
        if(op!=4){
            pos=read(),x=read();
            int ans=ask(1,1,n,pos);
            if(ans){
                if(pos<=n-1) change(1,1,n,pos+1,n,ans);
                change_single(1,1,n,pos,-ans);
                change_min(1,1,n,pos,-inf,inf);
            }
        }
        if(op==1){
            if(pos<=n-1) change(1,1,n,pos+1,n,-x);
            change_single(1,1,n,pos,x);
            change_min(1,1,n,pos,-inf,inf);
        }else if(op==2){
            int bef=0;
            if(pos>=2) bef=ask_sum(1,1,n,1,pos-1);
            change_min(1,1,n,pos,-inf,x-bef);
        }else if(op==3){
            int bef=0;
            if(pos>=2) bef=ask_sum(1,1,n,1,pos-1);
            change_min(1,1,n,pos,x-bef,inf);
        }else{
            x=read();
            if(a[1].l==a[1].r){
                printf("%lld\n",a[1].l+a[1].x);
                continue;
            }
            printf("%lld\n",max(min(x,a[1].r),a[1].l)+a[1].x);
        }
    }
}

              
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值