题解:吉司机线段树

题意

给定一个序列 \(a\)\(m\) 个操作:

  • 1 x:将 \(a_x\) 清零。

  • 2 x:将 \(1\le i\le n\) 的所有 \(a_i\) 全部赋值为 \(\max(a_i,x)\)

此外,还有 \(q\) 个删除操作,第 \(i\) 次会将第 \(p_i\) 个操作删除,删除是永久的。每次删除过后,请求出按顺序进行完剩余 \(m-i\) 个操作后,\(\sum\limits_{i=1}^n a_i\) 的值。

\(1\le n,m,q\le 3\times 10^5\),1.5s。

题解

删除太困难了,显然把删除倒过来转化为插入。

插入也不好做,因为你插入的时间是不固定的,所以考虑在时间轴建立数据结构(??),维护他对前后的操作的影响。

观察到一个位置只有最后一次清零操作有用。那我们考虑维护一个线段树,记录 \(\text{cnt}\) 表示当前区间内有多少个生效的清零操作,\(\text{mx}\) 表示当前区间的左端点到 \(m\) 的最大的取 \(\max\) 操作是多少。那显然答案可以表示为所有叶子的 \(\text{cnt}\times\text{mx}\) 之和,记为 \(\text{sum}\)

考虑一次操作会对其造成什么影响。分两个操作来看:

  • 在时间 \(i\) 插入一个操作 1 x

记位置 \(x\) 最后一次清零操作的时间为 \(\text{lst}_x\),则当 \(\text{lst}_x>i\) 时,这次操作不可能成为有效的清零操作。否则将 \(\text{cnt}_{\text{lst}_x} \to 0\)\(\text{cnt}_i \to 1\)\(\text{lst}_x \to i\)。单点修改很好维护。

  • 在时间 \(i\) 插入一个操作 2 x

因为 \(\text{mx}\) 维护的是后缀的 \(\max\),故它是单调不递增的。那一次修改,会将区间 \([\text{pos},i]\) 这段区间的 \(\text{mx}\) 全部赋值成 \(x\),其中 \(\text{pos}\) 是满足 \(\text{mx}_\text{pos}<x\) 的最小的时间。这个可以线段树二分出来,而区间赋值也是简单的。

最终要求的答案就是线段树根节点的 \(\text{sum}\)。复杂度 \(\mathcal O(n\log n)\)(假设 \(n,m,q\) 同阶)。

看似这道题做完了,但是还有一个问题:初始的 \(a_i\) 怎么办?如果一次清零操作都没有可能会对答案产生影响。

当然你可以将这些分开维护,但这没必要。我们可以将 \(a\) 排序,假设排序后的 \(a_i\) 的原始位置是 \(\text{id}_i\),那么可以依次在最开始加入 \(2n\) 个操作:第 \(2i-1\) 个操作插入 1 id[i],第 \(2i\) 个操作插入 2 a[i]。容易证明这是对的。

代码

#define int long long
int n,m,q,id[1000005],x[1000005],y[1000005],p[1000005],ans[1000005],lst[1000005];
pii a[1000005];
bool vis[1000005];
struct segtree{
    #define ls (x<<1)
    #define rs (x<<1|1)
    #define mid (l+r>>1)
    int tr[4000005],cnt[4000005],mx[4000005],lzy[4000005];
    void pushup(int x){tr[x]=tr[ls]+tr[rs],cnt[x]=cnt[ls]+cnt[rs],mx[x]=mx[ls];}
    void pushdown(int x){
        if(lzy[x])
            mx[ls]=lzy[ls]=lzy[x],tr[ls]=mx[ls]*cnt[ls],
            mx[rs]=lzy[rs]=lzy[x],tr[rs]=mx[rs]*cnt[rs],lzy[x]=0;
    }
    void updcnt(int x,int l,int r,int q,int k){
        if(l==r)return cnt[x]=k,tr[x]=cnt[x]*mx[x],void();
        pushdown(x);
        if(q<=mid)updcnt(ls,l,mid,q,k);
        else updcnt(rs,mid+1,r,q,k);
        pushup(x);
    }
    void updmx(int x,int l,int r,int ql,int qr,int k){
        if(ql<=l&&r<=qr)return mx[x]=lzy[x]=k,tr[x]=cnt[x]*mx[x],void();
        pushdown(x);
        if(ql<=mid)updmx(ls,l,mid,ql,qr,k);
        if(mid<qr)updmx(rs,mid+1,r,ql,qr,k);
        pushup(x);
    }
    int query(int x,int l,int r,int ql,int qr,int k){
        if(l==r)return mx[x]>=k?-1:l;
        pushdown(x);
        if(qr<=mid)return query(ls,l,mid,ql,qr,k);
        if(mid<ql)return query(rs,mid+1,r,ql,qr,k);
        if(mx[rs]<k){
            int tmp=query(ls,l,mid,ql,qr,k);
            return ~tmp?tmp:mid+1;
        }
        return query(rs,mid+1,r,ql,qr,k);
    }
}seg;
void solve(){
	cin>>n>>m>>q,m+=n*2;
	fo(i,1,n)cin>>a[i].fi,a[i].se=i;
	sort(a+1,a+1+n),reverse(a+1,a+1+n);
	fo(i,1,n)x[i*2-1]=1,y[i*2-1]=a[i].se,x[i*2]=2,y[i*2]=a[i].fi;
	fo(i,n*2+1,m)cin>>x[i]>>y[i];
	fo(i,1,q)cin>>p[i],p[i]+=2*n,vis[p[i]]=1;
    fo(i,1,m)if(!vis[i]){
        if(x[i]==1){
            if(lst[y[i]])seg.updcnt(1,1,m,lst[y[i]],0);
            seg.updcnt(1,1,m,i,1),lst[y[i]]=i;
        }
        else{
            int pos=seg.query(1,1,m,1,i,y[i]);
            if(~pos)seg.updmx(1,1,m,pos,i,y[i]);
        }
    }
    fd(i,q,1){
        ans[i]=seg.tr[1];
        if(x[p[i]]==1){
            if(p[i]>lst[y[p[i]]])
                seg.updcnt(1,1,m,lst[y[p[i]]],0),seg.updcnt(1,1,m,p[i],1),lst[y[p[i]]]=p[i];
        }
        else{
            int pos=seg.query(1,1,m,1,p[i],y[p[i]]);
            if(~pos)seg.updmx(1,1,m,pos,p[i],y[p[i]]);
        }
    }
    fo(i,1,q)cout<<ans[i]<<'\n';
}
posted @ 2026-08-23 17:18  qwertim  阅读(5)  评论(0)    收藏  举报