[ZJOI2013]K大数查询
曾经光辉无限的省选题......
竟然是二刷。
壹、题目
贰、题解
先考虑对于单个询问,我们只需要二分答案,然后看一下比这个答案大的数有多少即可。
然后我们将所有询问用整体二分来做,处理到当前值域区间 \([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\).