\(n\)才3万主席树个锤子哦。。。
介绍一种最简单的写法——归并树。
归并树是一种线段树,它的特殊之处在于,它的结点维护的信息并不是什么最大值区间和什么什么的,而是一个数列,代表该节点维护的区间排好序的结果。
由于这样的整棵线段树看上去就像归并排序一样,因此得名归并树
。
建树的复杂度和归并排序一样是\(O(n\log n)\)的,这里不提了。
查询的话,众所周知区间\([l,r]\)中小于等于\(k\)的数的个数相当于区间\([l,p]\)与区间\([p+1,r]\)中小于等于\(k\)的数的个数。这样就可以扔给两个(与这个区间有交集的)子结点做啦!但如果当前结点的区间被查询区间完全包含的话,那就不能推卸责任了,直接在这个结点的数列上二分查找即可找到答案了。查询复杂度\(O(\log^2 n)\)/次。
总复杂度:\(O(n\log n + q\log^2 n)\)
更具体的描述详见:https://strncmp.blog.luogu.org/solution-sp3946
#include
#include
#include
using namespace std;
const int N=1e5+7;
int Array[N];
namespace SGTree{
int L[N<<2],R[N<<2];
vector dat[N<<2];
void build(int l,int r,int rt=1)
{
L[rt]=l,R[rt]=r;
if(l==r)
{
dat[rt].resize(1);
dat[rt][0]=Array[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
dat[rt].resize(r-l+1);
merge(dat[rt<<1].begin(),dat[rt<<1].end(),
dat[rt<<1|1].begin(),dat[rt<<1|1].end(),
dat[rt].begin());
}
int query(int l,int r,int x,int rt=1)
{
if(l<=L[rt]&&R[rt]<=r)
return upper_bound(dat[rt].begin(),dat[rt].end(),x)
-dat[rt].begin();
int mid=(L[rt]+R[rt])>>1,ret=0;
if(l<=mid) ret+=query(l,r,x,rt<<1);
if(r>mid) ret+=query(l,r,x,rt<<1|1);
return ret;
}
}
int n,m;
signed main()
{
scanf("%d",&n);
for(register int i=1;i<=n;i++)
scanf("%d",Array+i);
SGTree::build(1,n);
sort(Array+1,Array+1+n);
int i,j,k;
scanf("%d",&m);
while(m--)
{
scanf("%d%d%d",&i,&j,&k);
printf("%d\n",(j-i+1)-SGTree::query(i,j,k));
}
return 0;
}
求赞QwQ。