[XJOI]栈

本文介绍了XJOI中涉及的单调栈问题,分析了如何利用离线方法和线段树进行求解,并详细阐述了动态维护单调栈和线段树的思路,最终实现了O(qlog2q)的时间复杂度解题方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

n个单调栈(单调递减)排成一排,一开始每个栈都是空的。
q个操作,每次会给下标在[l,r]内的栈都push一个x或者查询下标为k的栈里面所有数的和。

1n,q2×105,1x109


题目分析

考虑离线,把所有操作挂在对应端点上,然后从左向右扫描线。
以时间为下标建立线段树,考虑动态地维护当前扫到的单调栈在每一时间点的答案。
这个就十分套路了,和WC2013的楼房重建差不多。
先考虑如何查询,定义函数query([l,r],p)表示查询时间区间[l,r]组成的单调栈在最后加入p之后中所有数的和,查询时显然p=0调用一下就好了。
如果l=r,那么我们可以直接计算答案。
mid=l+r2rmx表示区间[mid+1,r]中所有数的最大值。
如果rmx>p,那么显然p不会对左半区间产生任何影响,而右边的最大值会加入到左边区间的单调栈中,答案等于query([l,mid],rmx)+query([mid+1,r],p)rmx
否则,右边的单调栈会直接被p全部弹出,答案等于query([l,mid],p)
如果我们一直这样递归下去,复杂度是没有保证的,怎么办呢?
fx表示x这个节点右半边区间最大值加入到左半边的单调栈之后,单调栈的所有数只和,即query(left.range,right.max)。先不考虑这个如何维护,反正我们每次修改之后线段树上每个位置的这个值都要修改过来。
假如我们的query操作中的区间已经是一个完整的线段树区间了,那么我们的rmx显然可以O(1)得到,query([l,mid],rmx)其实就是fx,也可以O(1)得到。
那么这个query的时间复杂度如何分析呢?
我们研究里面的嵌套函数调用了多少次。
首先,在我的区间还不是一个完整的线段树区间时,rmx需要通过调用区间最小值来得到,调用一次的复杂度是O(logq)的,而调用次数也是O(logq)的,因此这个复杂度是O(log2q)的。
其次,我们的查询操作会下放到O(logq)个完整的线段树区间,观察发现接下来我们往下走的过程已经不会增加新的分支,都是一条路走到底,因此每个区间最多会向下走O(logq)步,这个时间复杂度也是O(log2q)的。
至于fx怎么在每次修改之后重新得到?直接在update时调用一下query就好了。每次修改会调用O(logq)update,由于这时我的query都是在线段树上的完整区间上查,因此每次复杂度是O(logq)的。
于是总的时间复杂度就是O(qlog2q)的。这个套路真的很精彩。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(LL x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int Q=200050;
const int M=Q<<1;
const int S=Q<<2;

struct query
{
    int opt,x,ti,val;

    query (int opt_=0,int x_=0,int ti_=0,int val_=0){opt=opt_,x=x_,ti=ti_,val=val_;}

    bool operator<(query const qry)const{return x<qry.x||x==qry.x&&opt<qry.opt;}
}qy[M];

inline int max(int x,int y){return x>y?x:y;}

struct segment_tree
{
    int mx[S];
    LL f[S];

    int querymax(int x,int st,int en,int l,int r)
    {
        if (st==l&&en==r) return mx[x];
        int mid=l+r>>1;
        if (en<=mid) return querymax(x<<1,st,en,l,mid);
        else if (mid+1<=st) return querymax(x<<1|1,st,en,mid+1,r);
        else return max(querymax(x<<1,st,mid,l,mid),querymax(x<<1|1,mid+1,en,mid+1,r));
    }

    LL querysum(int x,int st,int en,int l,int r,int p)
    {
        if (l==r) return (mx[x]>p)*mx[x]+p;
        int mid=l+r>>1;bool cvr=st==l&&en==r;
        if (en<=mid) return querysum(x<<1,st,en,l,mid,p);
        else if (mid+1<=st) return querysum(x<<1|1,st,en,mid+1,r,p);
        else
        {
            int rmx=cvr?mx[x<<1|1]:querymax(x<<1|1,mid+1,en,mid+1,r);
            if (rmx>p) return (cvr?f[x]:querysum(x<<1,st,mid,l,mid,rmx))+querysum(x<<1|1,mid+1,en,mid+1,r,p)-rmx;
            else return querysum(x<<1,st,mid,l,mid,p);
        }
    }

    void update(int x,int l,int r)
    {
        mx[x]=max(mx[x<<1],mx[x<<1|1]);
        int mid=l+r>>1;
        f[x]=querysum(x<<1,l,mid,l,mid,mx[x<<1|1]);
    }

    void modify(int x,int y,int l,int r,int val)
    {
        if (l==r)
        {
            mx[x]=val;
            return;
        }
        int mid=l+r>>1;
        if (y<=mid) modify(x<<1,y,l,mid,val);
        else modify(x<<1|1,y,mid+1,r,val);
        update(x,l,r);
    }
}t;

int n,q,cnt,qid;
LL ans[Q];

void calc()
{
    sort(qy+1,qy+1+cnt);
    for (int cur=1,ptr;cur<=cnt;)
    {
        for (ptr=cur;cur<=cnt&&qy[cur].x==qy[ptr].x&&!qy[cur].opt;++cur) t.modify(1,qy[cur].ti,1,q,qy[cur].val);
        for (;cur<=cnt&&qy[cur].x==qy[ptr].x&&qy[cur].opt==1;++cur) ans[qy[cur].val]=t.querysum(1,1,qy[cur].ti,1,q,0);
        for (;cur<=cnt&&qy[cur].x==qy[ptr].x;++cur) t.modify(1,qy[cur].ti,1,q,0);
    }
}

int main()
{
    freopen("stack.in","r",stdin),freopen("stack.out","w",stdout);
    n=read(),q=read();
    for (int i=1;i<=q;++i)
    {
        int opt=read();
        if (opt==1)
        {
            int l=read(),r=read(),x=read();
            qy[++cnt]=query(0,l,i,x),qy[++cnt]=query(2,r,i,0);
        }
        else
        {
            int x=read();
            qy[++cnt]=query(1,x,i,++qid);
        }
    }
    calc();
    for (int i=1;i<=qid;++i) write(ans[i]),putchar('\n');
    fclose(stdin),fclose(stdout);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值