P7476 「C.E.L.U-02」苦涩 题解

P7476 「C.E.L.U-02」苦涩

题目大意:

有n个可重集合,初始为空;将执行 \(m\) 次操作;

操作有 \(3\) 种:

  1. \(l\)\(r\) 的集合中加入一个元素 \(k\)

  2. 删除 \(l\)\(r\) 集合中最大的元素,若该集合内有多个最大元素,则只删除一个,若 \(l\)\(r\) 集合都无元素,则无视

  3. 查询 \(l\)\(r\) 集合中最大的元素,若 \(l\)\(r\) 集合都无元素,则输出 \(-1\)

要求输出所有查询操作的答案。

算法一:暴力修改查询

维护 \(n\)\(multiset\) ,每次操作遍历 \(l\)\(r\) 的集合,暴力修改+查询。

时间复杂度:\(O(nm)\),期望得分:\(10pts\)

算法二:特殊性质A

对于 \(subtask2\) 来说,问题变成了区间赋较大值以及区间查最大值。

线段树维护区间最大,时间复杂度:\(O(n\log{n})\),期望得分:\(20pts\)

算法三:特殊性质B法一

对于 \(subtask3,5\) 来说,原先的区间删最大变成了单点删最大。

考虑分块,在每个块和点中维护一个大根堆,记录块内最大值。

删除时找到区间内最大的块,遍历块内单点,比较单点中的堆的堆顶和块中的堆的堆顶,

若单点中的更大,那么直接删除,更新单点和块内的最大值;

否则,删除后将块内每个堆都加上这个点的堆,无需更新最大值。

时间复杂度:\(O(n\sqrt{n}\log{n})\),期望得分:\(30pts\)

算法四:特殊性质B法二

有了大根堆的想法,那么我们可以在线段树的最底层节点处维护大根堆,并记录最大值

每次删除,一直搜到底,\(O(\log{n})\) 修改加上堆的 \(O(\log{n})\) 就是 \(O(\log^2{n})\)

时间复杂度:\(O(n\log^2{n})\),期望得分:\(30pts\)

算法五:线段树+堆

考虑在算法四的基础上加上 \(pushdown\) 操作;

在每个线段树节点都维护堆,父亲节点记录每次更改操作的值;

查询时就 \(pushdown\) 下来,删除时也直接在父亲节点的堆里面删除即可;

时间复杂度:\(O(n\log^2{n})\),期望得分:\(60\)~\(100pts\)

算法六:标记永久化

有了算法五 \(pushdown\) 操作的引入,可以想到标记永久化。

在下放修改操作时,用 \(\log{n}\) 的复杂度递归下放,删除同理,加上深度复杂度 \(O(\log^2{n})\)

时间复杂度:\(O((n+m)\log^2{n})\),期望得分:\(100pts\)

code

#include<bits/stdc++.h>
#define N 200005
#define inf 0x3f3f3f3f
#define endl '\n'
#define lc (p<<1)
#define rc (p<<1|1)
#define debug cerr<<__LINE__<<endl
using namespace std;
int n,m,tmp;
struct node{
	int maxn;priority_queue<int>q;
	inline void init(){q.emplace(maxn=-1);}//堆和最大值直接初始为-1,查询时就不需要特判了
}t[N<<2];
inline char gc(){
	static const int L=1<<22|1;static char c[L],*a,*b;
	return (a==b)&&(b=(a=c)+fread(c,1,L,stdin),a==b)?-1:*a++;
}
inline int read(){
	register int f=1,k=0;
	register char c=gc();
	while(c!='-'&&(c<'0'||c>'9')) c=gc();
	if(c=='-') f=-1,c=gc();
	while(c>='0'&&c<='9') k=(k<<3)+(k<<1)+(c^48),c=gc();
	return f*k;
}
inline void write(register int x){
    if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar((x%10)|48);
}
inline void build(const int p,const int l,const int r){
	t[p].init();if(l==r) return;
	const register int mid=((l+r)>>1);
	build(lc,l,mid);build(rc,mid+1,r);
}
inline void pushnow(const int p,const int k){t[p].q.emplace(k),t[p].maxn=max(t[p].maxn,k);}//修改当前节点
inline void pushup(const int p){t[p].maxn=max(max(t[lc].maxn,t[rc].maxn),t[p].q.top());}//向上传递最大值
inline void pushdown(const int p,const int l,const int r,const int x,const int y,const int k){
	if(x<=l&&r<=y) return;//只修改覆盖范围在x到y之外的节点
	const register int mid=((l+r)>>1);
	if(x>mid) pushnow(lc,k),pushdown(rc,mid+1,r,x,y,k);//若当前区间覆盖范围在目标区间左侧,那么修改左区间,继续向右区间搜索
	else if(y<=mid) pushnow(rc,k),pushdown(lc,l,mid,x,y,k);//同上
	else pushdown(lc,l,mid,x,y,k),pushdown(rc,mid+1,r,x,y,k);//两边都有就都搜
	return pushup(p);//回溯
}
inline void update(const int p,const int l,const int r,const int x,const int y,const int k){
	if(x<=l&&r<=y) return pushnow(p,k);//找到合法区间就改
	const register int mid=((l+r)>>1);
	if(x<=mid) update(lc,l,mid,x,y,k);
	if(y>mid) update(rc,mid+1,r,x,y,k);
	return pushup(p);
}
inline void delet(const int p,const int l,const int r,const int x,const int y,const int k){
	if(x<=l&&r<=y) if(t[p].maxn<k) return;//剪去无用搜索
	if(t[p].q.top()==k){//要删除的元素在这个区间内
		t[p].q.pop();//更新
		pushdown(p,l,r,x,y,k);//在这个区间内继续搜
		return l==r?void(t[p].maxn=t[p].q.top()):pushup(p);//更新最底部的点,回溯
	}
	const register int mid=((l+r)>>1);
	if(x<=mid) delet(lc,l,mid,x,y,k);
	if(y>mid) delet(rc,mid+1,r,x,y,k);
	return pushup(p);
}
inline int query(const int p,const int l,const int r,const int x,const int y){
	if(x<=l&&r<=y) return t[p].maxn;
	const register int mid=((l+r)>>1);
	register int ans=t[p].q.top();
	if(x<=mid) ans=max(ans,query(lc,l,mid,x,y));
	if(y>mid) ans=max(ans,query(rc,mid+1,r,x,y));
	return ans;
}
main(void){
    n=read();m=read();
	t[0].init();build(1,1,n);
	while(m--){
		const register int op=read(),x=read(),y=read();
		if(op==1) update(1,1,n,x,y,read());
		else if(op==2) ~(tmp=query(1,1,n,x,y))?delet(1,1,n,x,y,tmp):void();
		else if(op==3) write(query(1,1,n,x,y)),putchar('\n');
	}
    return 0;
}
posted @ 2021-10-29 21:44  Noir'  阅读(55)  评论(0)    收藏  举报