写一种数据结构维护一些数据
包括插入,查找,删除,查找前驱,查找后继等
数据范围:数的个数 n ≤ 1 0 6 n\leq 10^6 n≤106
其实我们只需要写出一种数据结构满足查询排名为 p p p的数, p p p这个数的排名,以及支持修改
的数据结构即可
那前驱和后继咋办呢?
p r e ( x ) = k t h ( r k ( x ) − 1 ) , n x t ( x ) = k t h ( r k ( x + 1 ) ) pre(x)=kth(rk(x)-1),nxt(x)=kth(rk(x+1)) pre(x)=kth(rk(x)−1),nxt(x)=kth(rk(x+1))
关于上述结论证明方法很多,这里不再阐述。
权值线段树或者树状数组套二分就可以解决这个问题,由于本题是单点修改,所以相对而言树状数组会好写很多,但用下标建一样,线段树普遍比树状数组好拓展很多
,所以这里主要介绍的是权值线段树的做法
思想都很简单,就是在值域上建线段树(树状数组),线段树利用分治的思想,树状数组利用二进制差分前缀和的思想,我们就可以统计对于一个数值 x x x,有多少个数比它小(树状数组直接查询,权值线段树递归下去),同理可以得到这个数的排名
至于查询排名为 p p p的数
权值线段树维护一个 s u m sum sum表示对于区间的区间和, s u m [ 1 ] sum[1] sum[1]表示 [ m i n l . ∼ m a x r ] [minl.\sim maxr] [minl.∼maxr]的和, s u m [ 2 ] sum[2] sum[2]表示 [ m i n l ∼ ( m i n l + m a x r ) ] [minl\sim(minl+maxr)] [minl∼(minl+maxr)], s u m [ 3 ] sum[3] sum[3]表示 [ ( m i n l + m a x r ) / 2 + 1 ∼ m a x r ] [(minl+maxr)/2+1\sim maxr] [(minl+maxr)/2+1∼maxr]的和,以此类推
如果左区间的数的个数大于 p p p,即 s u m [ l s o n ] > = p sum[lson]>=p sum[lson]>=p,说明这个数一定在左区间,则往左区间走
否则,说明排名为 p p p的数一定在右区间,此时就相当于在右子树查询排名为 p − s u m [ l s o n ] p-sum[lson] p−sum[lson]的数的排名
备注:上面这句话很重要!!!最好理解完了再看代码!
到这问题就基本解决,每个数的范围 ∣ a i ∣ ≤ 1 0 7 |a_i|\leq 10^7 ∣ai∣≤107,所以酱紫做的复杂度为 O ( m l o g ( 2 × 1 0 7 ) ) ≈ O ( 2.43 × 1 0 7 ) O(mlog(2\times 10^7))\approx O(2.43\times 10^7) O(mlog(2×107))≈O(2.43×107)足够通过本题
离线(离散化)之后可以跑得更快,时间复杂度就变成了 O ( m l o g m ) ≈ O ( 2.0 × 1 0 7 ) O(mlogm)\approx O(2.0\times 10^7) O(mlogm)≈O(2.0×107)
本代码是离散化后的代码
#include
#include
#include
#define N 400010
#define maxn 100000
#define lson k<<1
#define rson k<<1|1
using namespace std;int sum[N],n,m,k,a[maxn+10],b[maxn+10],op[maxn+10],tot;
long long ans;
inline long long read()
{
char c;int d=1;long long f=0;
while(c=getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
inline void add(int x,int d,int l=1,int r=tot,int k=1)//单点修改
{
sum[k]+=d;
if(l==r) return;
int mid=l+r>>1;
if(x<=mid) add(x,d,l,mid,lson);//在左边
else add(x,d,mid+1,r,rson);//在右边
return;
}
inline int kth(int p,int l=1,int r=tot,int k=1)//查询排名为k的数
{
if(l==r) return l;
int s=sum[lson],mid=l+r>>1;
if(s>=p) return kth(p,l,mid,lson);//在左子树
return kth(p-s,mid+1,r,rson);//在右子树
}
inline int rank(int p,int l=1,int r=tot,int k=1)
{
if(l==r) return 1;
int s=sum[lson],mid=l+r>>1;
if(p<=mid) return rank(p,l,mid,lson);//在左子树
return s+rank(p,mid+1,r,rson);//在右子树,注意此时它的排名要加上sum[lson],因为这些数都比它小!!!
}
signed main()
{
m=read();
for(register int i=1;i<=m;i++)
{
op[i]=read();a[i]=read();
if(op[i]!=4) b[++tot]=a[i];
}
sort(b+1,b+1+tot);
tot=unique(b+1,b+1+tot)-b-1;
for(register int i=1;i<=m;i++) if(op[i]!=4) a[i]=lower_bound(b+1,b+1+tot,a[i])-b;//离散化
for(register int i=1;i<=m;i++)
{
if(op[i]==1) add(a[i],1);
if(op[i]==2) add(a[i],-1);
if(op[i]==3) printf("%d\n",rank(a[i]));
if(op[i]==4) printf("%d\n",b[kth(a[i])]);
if(op[i]==5) printf("%d\n",b[kth(rank(a[i])-1)]);
if(op[i]==6) printf("%d\n",b[kth(rank(a[i]+1))]);
}
}