题目描述:
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改某一位值上的数值
4.查询k在区间内的前驱(前驱定义为严格小于x,且最大的数,若不存在输出-2147483647)
5.查询k在区间内的后继(后继定义为严格大于x,且最小的数,若不存在输出2147483647)
#注意上面两条要求和tyvj或者bzoj不一样,请注意
输入描述:
第一行两个数 n,m 表示长度为n的有序序列和m个操作
第二行有n个数,表示有序序列
下面有m行,opt表示操作标号
若opt=1 则为操作1,之后有三个数l,r,k 表示查询k在区间[l,r]的排名
若opt=2 则为操作2,之后有三个数l,r,k 表示查询区间[l,r]内排名为k的数
若opt=3 则为操作3,之后有两个数pos,k 表示将pos位置的数修改为k
若opt=4 则为操作4,之后有三个数l,r,k 表示查询区间[l,r]内k的前驱
若opt=5 则为操作5,之后有三个数l,r,k 表示查询区间[l,r]内k的后继
输出描述:
对于操作1,2,4,5各输出一行,表示查询结果
输入样例:
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
输出样例:
2
4
3
4
9
核心思想:
感谢:https://blog.csdn.net/a_forever_dream/article/details/100875569
题目传送门
前置知识:
离散化,动态开点new,树状数组,线段树。
涉及到线段树时,“区间”有两种:
1、位置区间(入门线段树中的mid将位置区间一分为二)
2、数值区间(权值线段树中的mid将数值区间一分为二)
静态主席树又称可持久化的权值线段树:只查询不修改的区间第K大。
如果要求有修改操作的话,就成动态主席树了,准确的名称为动态开点的线段树套树状数组,简称树套树。
静态主席树之所以可持久化是因为每棵权值线段树的建立是参照上一棵线段树的,每次建树只需要新建一条链。每棵树维护的位置区间是[1,i]。
动态主席树会被修改,如果要修改位置i的值,那么第i到n棵树都需要被修改,复杂度太高,所以我们建树时不能再参照上一棵树来建树。
然而,我们又要维护权值的区间和(也就是维护数在位置区间[x,y]上且在数值区间[l,r]内出现的次数),谁可以log级的维护区间和呢?树状数组说它可以!
那么树套树数据结构中的每棵线段树维护的位置区间不是[1,i]了,而是根据树状数组的lowbit技巧来维护某些特定位置。
代码如下:
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=1e5+20;
int n,m,a[N];
int b[N],cb;//用于离散
struct Node{
int id,x,y,z;
}q[N];
struct node{
int z;
node *zuo,*you;
node():z(0),zuo(NULL),you(NULL){}
};
node *root[N];
//用法同树状数组
int lowbit(int x)
{
return x&(-x);
}
//now是当前结点,l,r是结点now管辖的数值区间,目的是数值k的权值+v
void change(node *&now,int l,int r,int k,int v)
{
if(now==NULL)now=new node();
now->z+=v;
if(l==r)
return;
int mid=(l+r)>>1;
if(k<=mid)
change(now->zuo,l,mid,k,v);
else
change(now->you,mid+1,r,k,v);
return;
}
//now是当前结点,l,r是结点now管辖的数值区间,目的是返回数值区间[x,y]的权值和
int sum(node *now,int l,int r,int x,int y)
{
if(now==NULL)return 0;
if(l==x&&r==y)
return now->z;
int mid=(l+r)>>1;
if(y<=mid)
return sum(now->zuo,l,mid,x,y);
return sum(now->you,mid+1,r,x,y);
}
//得到位置区间[x,y],数值区间[l,r]的区间和
int getsum(int x,int y,int l,int r)
{
int re=0;
for(int i=y;i>0;i-=lowbit(i))
re+=sum(root[i],1,cb,l,r);
for(int i=x;i>0;i-=lowbit(i))
re-=sum(root[i],1,cb,l,r);
return re;
}
//返回位置区间[x,y],数值区间[l,r]内,排名第z的数值
int ask(int x,int y,int l,int r,int z)
{
if(l==r)
return l;
int mid=(l+r)>>1;
int te=getsum(x,y,l,mid);
if(z<=te)
return ask(x,y,l,mid,z);
return ask(x,y,mid+1,r,z-te);
}
//返回位置区间[x,y],数值区间[l,r]内,数值z的排名
int query(int x,int y,int l,int r,int z)
{
if(l==r)
return 1;
int mid=(l+r)>>1;
if(z<=mid)
return query(x,y,l,mid,z);
return getsum(x,y,l,mid)+query(x,y,mid+1,r,z);
}
//返回位置区间[x,y]内数值z的前驱
int pre(int x,int y,int z)
{
int rank=query(x,y,1,cb,z);
rank--;//无论原区间含不含z,rank-1都会是他前驱,除非z自身为极小值
if(!rank)return cb+1;
return ask(x,y,1,cb,rank);
}
//返回位置区间[x,y]内数值z的后继
int nxt(int x,int y,int z)
{
int rank=query(x,y,1,cb,z);
if(rank==y-x+1)return cb+2;//z本身都放不下了,更别说后继了
if(ask(x,y,1,cb,rank)==z)rank++;//需要判断原区间含不含z
if(rank==y-x+1)return cb+2;//z本身是此区间内的极值,无更大值
return ask(x,y,1,cb,rank);
}
//用于离散
int getid(int x)
{
return lower_bound(b+1,b+cb+1,x)-b;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[++cb]=a[i];
}
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&q[i].id,&q[i].x,&q[i].y);
if(q[i].id!=3)
{
scanf("%d",&q[i].z);
if(q[i].id!=2)
b[++cb]=q[i].z;
}
else
b[++cb]=q[i].y;
}
//离散
sort(b+1,b+cb+1);
cb=unique(b+1,b+cb+1)-b-1;
//处理不存在的情况
b[cb+1]=-2147483647;
b[cb+2]=2147483647;
//建初始树
for(int i=1;i<=n;i++)
{
int te=getid(a[i]);
for(int j=i;j<=n;j+=lowbit(j))
change(root[j],1,cb,te,1);
}
//操作
for(int i=0;i<m;i++)
{
int x=q[i].x,y=q[i].y,z=q[i].z,te;
switch(q[i].id)
{
case 1:
printf("%d\n",query(x-1,y,1,cb,getid(z)));//直接输出排名,不必数组b映射
break;
case 2:
printf("%d\n",b[ask(x-1,y,1,cb,z)]);//此处z是排名,不getid
break;
case 3:
te=getid(a[x]);
for(int i=x;i<=n;i+=lowbit(i))
change(root[i],1,cb,te,-1);
a[x]=y;
te=getid(a[x]);
for(int i=x;i<=n;i+=lowbit(i))
change(root[i],1,cb,te,1);
break;
case 4:
printf("%d\n",b[pre(x-1,y,getid(z))]);
break;
case 5:
printf("%d\n",b[nxt(x-1,y,getid(z))]);
}
}
return 0;
}