题解 P3332 【[ZJOI2013]K大数查询】
I_AM_HelloWord · · 题解
此题有三种做法:
1.平衡树套线段树
2.线段树套线段树
3.整体二分
这里介绍一下后两种做法。
我先写了个200+行的线段树套平衡树(注意顺序),然后效率是
2.线段树套线段树
为什么线段树套平衡树是
因此,这里,我们的线段树套线段树,确切的说应该是权值线段树套区间线段树。
外层是权值线段树,实质上就是一个二分答案的过程,然后运用类似归并树和主席树的思想,我们设询问区间为
还有就是防止MLE,在区间线段树上搞个动态开点就好了。
算法设计基本上就是这样了,具体细节就看代码了。
参考代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<iostream>
#include<ctime>
#define rint register int
using namespace std;
typedef long long ll;
const int inf = 0x7fffffff;
const int N=5e4+5;
const int Tree=N*400;
int n,m,node=0,totn;
int ql[N],qr[N],op[N],b[N],k[N];
int rt[N<<2],tag[Tree];
ll sz[Tree];
struct Tnode{
int L,R;
}T[Tree];
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
#define lc p<<1
#define rc p<<1|1
inline void pushdown(int p,int l,int r){
int &v=tag[p],mid=(l+r)>>1;
if (!T[p].L)T[p].L=++node;
if (!T[p].R)T[p].R=++node;
tag[T[p].L]+=v,tag[T[p].R]+=v;
sz[T[p].L]+=v*(mid-l+1),sz[T[p].R]+=v*(r-mid);
v=0;
}
inline void update(int &p,int ql,int qr,int l=1,int r=n){
if (!p)p=++node;
if (ql<=l && r<=qr){
++tag[p];sz[p]+=r-l+1;
return;
}
if (tag[p])pushdown(p,l,r);
rint mid=(l+r)>>1;
if (ql<=mid) update(T[p].L,ql,qr,l,mid);
if (mid<qr) update(T[p].R,ql,qr,mid+1,r);
sz[p]=sz[T[p].L]+sz[T[p].R];
}
inline ll getsum(int &p,int ql,int qr,int l=1,int r=n){
if (!p)return 0;
if (ql<=l && r<=qr) return sz[p];
if (tag[p])pushdown(p,l,r);
rint mid=(l+r)>>1;
ll tt=0;
if (ql<=mid) tt+=getsum(T[p].L,ql,qr,l,mid);
if (mid<qr) tt+=getsum(T[p].R,ql,qr,mid+1,r);
return tt;
}
inline void add(int ql,int qr,int k,int p=1,int l=1,int r=totn){
update(rt[p],ql,qr);
if (l==r) return;
rint mid=(l+r)>>1;
if (k<=mid) add(ql,qr,k,lc,l,mid);
else add(ql,qr,k,rc,mid+1,r);
}
inline int query(int ql,int qr,ll k,int p=1,int l=1,int r=totn){
if (l==r) return b[l];
rint mid=(l+r)>>1;
ll tt=getsum(rt[rc],ql,qr);
if (tt<k) return query(ql,qr,k-tt,lc,l,mid);
else return query(ql,qr,k,rc,mid+1,r);
}
int main(){
n=read(),m=read();
for (rint i=1;i<=m;++i){
op[i]=read(),ql[i]=read(),qr[i]=read(),k[i]=read();
if (op[i]==1)b[++totn]=k[i];
}
sort(b+1,b+totn+1);
totn=unique(b+1,b+totn+1)-b-1;
for (rint i=1;i<=m;++i)
if (op[i]==1)k[i]=lower_bound(b+1,b+totn+1,k[i])-b;
for (rint i=1;i<=m;++i){
if (op[i]==1){
add(ql[i],qr[i],k[i]);
}else{
printf("%d\n",query(ql[i],qr[i],k[i]));
}
}
return 0;
}
3.整体二分
这应该属于练整体二分的一道比较基础的题目了。
何谓整体二分?就是直接一起二分所有的询问操作的答案,然后暴力扫描当前操作区间,将其划分为答案的左子区间与右子区间两个部分。
那么以什么为划分依据呢?看看这个操作对于左子区间有没有贡献。如果没有,那么就划分到右子区间中,然后将这个操作的权值更改为这个贡献减去所需的贡献,反之,则划分到左子区间中,同时将这个操作的贡献加入某一个容器,为询问操作服务。
这么说可能有点晕。就这道题说的话,应该是这样:
我们设尚未解决的操作区间为
则若该操作是添加操作,如果其添加的
反之,则将操作加入到右子区间中。
若该操作是询问操作,如果当前的
参考代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#define rint register int
using namespace std;
typedef long long ll;
const int N=5e4+5;
struct Ask{
int op,l,r,id;
ll v;
}q[N],tl[N],tr[N];
int tag[N<<2],rec[N<<2],ans[N];
ll sum[N<<2];
int n,m,Q;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0' || ch>'9'){if (ch=='-')f=-1;ch=getchar();}
while ('0'<=ch && ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
#define lc p<<1
#define rc p<<1|1
inline void pushdown(int p,int l,int r){
if (rec[p]){
rec[p]=0;
tag[lc]=tag[rc]=sum[lc]=sum[rc]=0;
rec[lc]=1,rec[rc]=1;
}
if (tag[p]){
rint mid=(l+r)>>1;
tag[lc]+=tag[p],tag[rc]+=tag[p];
sum[lc]+=tag[p]*(mid-l+1);
sum[rc]+=tag[p]*(r-mid);
tag[p]=0;
}
}
inline void add(int ql,int qr,int w,int p=1,int l=1,int r=n){
if (ql<=l && r<=qr){
tag[p]+=w;sum[p]+=w*(r-l+1);
return;
}
if (tag[p] || rec[p])pushdown(p,l,r);
rint mid=(l+r)>>1;
if (ql<=mid) add(ql,qr,w,lc,l,mid);
if (mid<qr) add(ql,qr,w,rc,mid+1,r);
sum[p]=sum[lc]+sum[rc];
}
inline ll query(int ql,int qr,int p=1,int l=1,int r=n){
if (ql<=l && r<=qr)return sum[p];
rint mid=(l+r)>>1;
ll tt=0;
if (tag[p] || rec[p])pushdown(p,l,r);
if (ql<=mid) tt+=query(ql,qr,lc,l,mid);
if (mid<qr) tt+=query(ql,qr,rc,mid+1,r);
return tt;
}
inline void solve(int st,int en,int l,int r){
if (l==r){
for (rint i=st;i<=en;++i)
if (q[i].op==2) ans[q[i].id]=l;
return;
}
rint mid=(l+r)>>1;
bool fl=0,fr=0;
rint L=0,R=0;
rec[1]=1;tag[1]=sum[1]=0;
for (rint i=st;i<=en;++i)
if (q[i].op==1){
if (q[i].v>mid){
add(q[i].l,q[i].r,1);
tr[++R]=q[i];
}else
tl[++L]=q[i];
}else{
ll val=query(q[i].l,q[i].r);
if (val<q[i].v){
q[i].v-=val;
fl=1;
tl[++L]=q[i];
}else{
fr=1;
tr[++R]=q[i];
}
}
for (rint i=1;i<=L;++i) q[st+i-1]=tl[i];
for (rint i=L+1;i<=L+R;++i) q[st+i-1]=tr[i-L];
if (fl) solve(st,st+L-1,l,mid);
if (fr) solve(st+L,en,mid+1,r);
}
int main(){
n=read(),m=read();
for (rint i=1;i<=m;++i){
q[i].op=read(),q[i].l=read(),q[i].r=read(),q[i].v=read();
if (q[i].op==2)q[i].id=++Q;
}
solve(1,m,-n,n);
for (rint i=1;i<=Q;++i)
printf("%d\n",ans[i]);
return 0;
}
貌似一比,树套树还好写一点哈?