1 条题解

  • 0
    @ 2025-8-24 22:30:14

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar abruce
    I won't feel lonely, nor will I be sorrowful... not before everything is buried.

    搬运于2025-08-24 22:30:14,当前版本为作者最后更新于2021-02-23 18:40:32,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    如果数据水,敬请谅解,也希望提供强数据。
    如果有时间复杂度更优秀的做法,请写题解,我会感激的。
    先看部分分吧。

    10pts

    每个点开一个堆,然后就是常规操作了。

    20pts

    只有添加,没有删除,相当于区间取 max,区间 max。那就意味着我们可以用线段树维护。

    40pts

    发现单点删除最大值用线段树不太好操作,也不好进行标记下放和信息。上传我们考虑暴力一点,用分块维护信息,每个块开一个堆代表整块都被插入的东西,每个点也要开一个堆,然后开一个 maxx 数组代表块内最大值。
    插入的时候我们边角暴力,中间插入整块的堆,然后更新 maxx。
    删除的时候我们比较它自己的堆和它所属的块的堆。如果他自己的堆的堆顶更大,则直接删除,然后更新 maxx。如果它所属的堆更大,则删除掉之后,把它塞入块内其余点的堆里,因为它们还存在这个值,所以不用更新 maxx。
    查询的时候我们边角暴力询问单点堆顶和整块堆顶,中间的整块直接查询 maxx,就解决了。
    时间复杂度在块长为 n\sqrt{n} 时复杂度较优,为 O(nnlogn)O(n\sqrt{n}logn)。代码如下,比较短。本来这是这个题的标算,但是……

    60pts(1)

    既然要 polylog,首先堆的一个 log 很难避免,我们剩下的那个 log 就可以往线段树上想。
    考虑使用标记永久化,这个永久化的标记形成了一个堆。我们再在线段树里维护一个 maxx,代表这个区间的最大值。这样,插入和查询操作就很好处理了。
    现在我们考虑删除,由于是单点,首先我们找到这个最大值,然后在线段树里找到这个标记。我们需要把这个标记移除,并将其加入这个区间的其它点内,你可以再写一个 pushdown,也可以直接跑两遍插入(这样常数要大一点)。
    时间复杂度 O(nlog2n)O(nlog^2n),代码挺好写的。
    我终于知道 lxl 为什么直接拒了。

    60pts(2)

    如果你的分块可以支持区间修改,则可以通过 60 分的数据。

    100pts

    我们考虑怎么优化 60 分的线段树,使它支持区间修改。
    我们可以想到先找到这一段的最大值是哪一个,然后把标记从这个区间上面移除。这样做看起来时间复杂度是不对的,但我们可以进行剪枝。如果这一个区间的最大值比我们需要删去的小,我们就不去了。
    乍一看复杂度还是不对,但我们可以分析一下。我们时间复杂度相当于对于每一个最大值的连续区间,把它在线段树上分成不超过 log 个区间。由于我们插入的时候是整段一起插入同一个值,所以插入均摊下来会产生 mlognmlogn 个线段树区间。
    我们每删除一个产生的线段树区间,最多花 lognlogn 时间递归下去删除标记。所以删除的时间复杂度为均摊 O(mlog2n)O(mlog^2n) (堆的复杂度和向下递归的复杂度是并列的)由于插入和查询的复杂度也是这个量级,所以总复杂度 O((n+m)log2n)O((n+m)log^2n)
    代码十分好写,不到 2k。如果哪里没叙述清楚,请向我提出。

    #include <bits/stdc++.h>
    #define lc id<<1
    #define rc id<<1|1
    using namespace std;
    const int maxn=2e5+5;
    struct tree {
    	int l,r,mid,maxx;
    	priority_queue<int> q;
    } t[maxn*4];
    int n,m;
    void build(int id,int l,int r) {
    	t[id]=(tree) {
    		l,r,(l+r)/2,-1
    	};
    	t[id].q.push(-1);
    	if(l==r)return;
    	int mid=(l+r)/2;
    	build(lc,l,mid);
    	build(rc,mid+1,r);
    }
    void addtag(int id,int v) {
    	t[id].q.push(v);
    	t[id].maxx=max(t[id].maxx,v);
    }
    void pushup(int id) {
    	t[id].maxx=max(max(t[lc].maxx,t[rc].maxx),t[id].q.top());
    }
    void pushdown(int id,int l,int r,int k) {//把 k 这个标记往 l,r 之外区间插入
    	if(t[id].l>=l&&t[id].r<=r) return;
    	if(l>t[id].mid)addtag(lc,k),pushdown(rc,l,r,k);
    	else if(r<=t[id].mid)addtag(rc,k),pushdown(lc,l,r,k);
    	else pushdown(lc,l,r,k),pushdown(rc,l,r,k);
    	pushup(id);
    }
    void add(int id,int l,int r,int v) {
    	if(t[id].l>=l&&t[id].r<=r) {
    		addtag(id,v);
    		return;
    	}
    	if(l<=t[id].mid)add(lc,l,r,v);
    	if(r>t[id].mid)add(rc,l,r,v);
    	pushup(id);
    }
    void del(int id,int l,int r,int k) {
    	if(t[id].l>=l&&t[id].r<=r&&t[id].maxx<k)return;//核心:剪枝
    	if(t[id].q.top()==k) {
    		t[id].q.pop();
    		pushdown(id,l,r,k);
    		if(t[id].l==t[id].r)t[id].maxx=t[id].q.top();
    		else pushup(id);
    		return;//由于只pop一个,这里弄完标记就走了
    	}
    	if(l<=t[id].mid)del(lc,l,r,k);
    	if(r>t[id].mid)del(rc,l,r,k);
    	pushup(id);
    }
    int ask(int id,int l,int r) {
    	if(t[id].l>=l&&t[id].r<=r) {
    		return t[id].maxx;
    	}
    	int ans=t[id].q.top();
    	if(l<=t[id].mid)ans=max(ans,ask(lc,l,r));
    	if(r>t[id].mid)ans=max(ans,ask(rc,l,r));
    	return ans;
    }
    int main() {
    	t[0].q.push(-1),t[0].maxx=-1;
    	int op,x,y,z;
    	scanf("%d%d",&n,&m);
    	build(1,1,n);
    	for(register int i=1; i<=m; i++) {
    		scanf("%d%d%d",&op,&x,&y);
    		switch(op) {
    			case 1: {
    				scanf("%d",&z);
    				add(1,x,y,z);
    				break;
    			}
    			case 2: {
    				z=ask(1,x,y);
    				if(z!=-1)del(1,x,y,z);
    				break;
    			}
    			case 3: {
    				printf("%d\n",ask(1,x,y));
    				break;
    			}
    		}
    	}
    	return 0;
    }
    
    • 1

    信息

    ID
    6291
    时间
    2000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者