[ZJOI2013]K大数查询

曾经光辉无限的省选题......

竟然是二刷。

壹、题目

传送门 to LUOGU

贰、题解

先考虑对于单个询问,我们只需要二分答案,然后看一下比这个答案大的数有多少即可。

然后我们将所有询问用整体二分来做,处理到当前值域区间 \([l,r]\),也就相当于我们二分了一个 \(mid\),对于这个 \(mid\),将所有操作一中插入值大于 \(mid\) 的操作对应的区间整体加一,对于询问操作直接查询即可。

但是需要注意一个细节 —— 当询问操作被下放到左区间时,对应查询的 \(rank\) 应该要减去这一次二分后查询的结果,这个可以类比平衡树。

叁、代码

using namespace Elaina;

const int maxn=5e4;
const int maxm=5e4;
const ll maxa=(1ll<<62)-1+(1ll<<62);

namespace tree{
	ll cnt[maxn<<2|2],tag[maxn<<2|2];
	int rec[maxn<<2|2];
	#define ls (i<<1)
	#define rs (i<<1|1)
	#define mid ((l+r)>>1)
	#define _this i,l,r
	#define _lq ls,l,mid
	#define _rq rs,mid+1,r
	inline void add(const int x,const int i,const int l,const int r){
		tag[i]+=x,cnt[i]+=1ll*(r-l+1)*x;
	}
	inline void clear(const int i){
		cnt[i]=tag[i]=0,rec[i]=1;
	}
	inline void pushdown(const int i,const int l,const int r){
		if(rec[i])clear(ls),clear(rs),rec[i]=0;
		if(tag[i]){
			add(tag[i],_lq);add(tag[i],_rq);
			tag[i]=0;
		}
	}
	inline void pushup(const int i){
		cnt[i]=cnt[ls]+cnt[rs];
	}
	void modify(const int L,const int R,const int i,const int l,const int r){
		if(L<=l && r<=R)return add(1,_this);
		if(rec[i] || tag[i])pushdown(_this);
		if(L<=mid)modify(L,R,_lq);
		if(mid<R)modify(L,R,_rq);
		pushup(i);
	}
	ll query(const int L,const int R,const int i,const int l,const int r){
		if(L<=l && r<=R)return cnt[i];
		if(rec[i] || tag[i])pushdown(_this);
		ll ret=0;
		if(L<=mid)ret+=query(L,R,_lq);
		if(mid<R)ret+=query(L,R,_rq);
		return ret;
	}
	#undef ls
	#undef rs
	#undef mid
	#undef _this
	#undef _lq
	#undef _rq
}

struct option{
	int type,l,r,id;ll c;
	option(){}
	option(const int T,const int L,const int R,const ll C):type(T),l(L),r(R),c(C){}
};
option opt[maxm+5];
option tmpl[maxm+5],tmpr[maxm+5];

int n,m;

int qcnt;// the counter of query
int ans[maxn+5];

inline void input(){
	n=readin(1),m=readin(1);
	int a,b,c;ll d;
	rep(i,1,m){
		a=readin(1),b=readin(1),c=readin(1),d=readin(1ll);
		opt[i]=option(a,b,c,d);
		if(a==2)opt[i].id=++qcnt;
	}
}

void solve(const int l,const int r,const int ql,const int qr){
	if(ql>qr)return;
	if(l==r){
		for(int i=ql;i<=qr;++i)if(opt[i].type==2)
			ans[opt[i].id]=l;
		return;
	}
	int mid=(l+r)>>1,cntl=0,cntr=0;
	tree::clear(1);
	for(int i=ql;i<=qr;++i){
		if(opt[i].type==1){
			if(opt[i].c>mid){
				tree::modify(opt[i].l,opt[i].r,1,1,n);
				tmpr[++cntr]=opt[i];
			}else tmpl[++cntl]=opt[i];
		}
		else{
			ll ret=tree::query(opt[i].l,opt[i].r,1,1,n);
			// the value mid is too big
			if(ret<opt[i].c){
				opt[i].c-=ret;
				tmpl[++cntl]=opt[i];
			}
			// else the value mid is too small
			else tmpr[++cntr]=opt[i];
		}
	}
	for(int i=ql;i<=ql+cntl-1;++i)opt[i]=tmpl[i-ql+1];
	for(int i=ql+cntl;i<=qr;++i)opt[i]=tmpr[i-ql-cntl+1];
	solve(l,mid,ql,ql+cntl-1);
	solve(mid+1,r,ql+cntl,qr);
}

signed main(){
	input();
	solve(-n,n,1,m);
	rep(i,1,qcnt)writc(ans[i],'\n');
	return 0;
}

肆、用到的小 \(\tt trick\)

除了整体二分之外,还用到一个比较经典的 \(\tt trick\).

先二分一个值,对于这个值来说,大于它的记为 \(1\),其他看做 \(0\),这样就将原来参差不齐的数组变成了 \(01\) 数组,比起原来的数组更好处理,如果询问单点只需要再加上一个 \(\log\).

posted @ 2021-02-17 19:02  Arextre  阅读(42)  评论(0编辑  收藏  举报