题意:
主席树模板:
给定一个数组,每次查询输入区间[L,R]的第k小的数。
题解:
主席树理解:
如有序列 1 2 5 1 3 2 2 5 1 2
我们要求 [5,10]第5小的数
(数列中不存在4、6、7、8 但根据原理就都写出来了,为方便理解,去掉了hash的步骤,实际的代码中其实只要一棵4个叶子节点的树即可)
(红色的为个数)
我们建立的[1, l-1](也就是[1, 4])之间的树为
[1, r]也就是[1,10]的树为
两树相减得到
我们来找第5小的数:
发现左子树为5 所以第5小的数在左边,再往下(左4右1) 发现左边小于5了,所以第5小的数在右边所以第5小的数就是3了
同样的,我们只要建立[1,i] (i是1到n之间的所有值)的所有树,每当询问[l, r]的时候,只要用[1, r]的树减去[1, l-1]的树,再找第k小就好啦
我们将这n个树看成是建立在一个大的线段树里的,也就是这个线段树的每个节点都是一个线段树(——这就是主席树)
最初所有的树都是空树,我们并不需要建立n个空树,只要建立一个空树,也就是不必每个节点都建立一个空树
插入元素时,我们不去修改任何的结点,而是返回一个新的树(——这就是函数式线段树)
因为每个节点都不会被修改,所以可以不断的重复用,因此插入操作的复杂度为O(logn)
总的复杂度为O((n+m)lognlogN)
你以为这样就结束了吗!!
你没有发现这样空间大到爆炸吗!!!
你在每个节点都建了一个线!段!树!这不MLE才有鬼呢!!!
那怎么办呢?
TiTi表示一棵[1, i]区间的线段树
那么TiTi与Ti−1Ti−1的区别就只有当前插入的这个元素aiai以及它的父亲以及他父亲的父亲以及他父亲的父亲的父亲...
也就是改变的就只有他和他上面logn个数
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define lson l, m
#define rson m+1, r
const int N=1e5+5;
int L[N<<5], R[N<<5], sum[N<<5];
int tot;
int a[N], T[N], Hash[N];
int build(int l, int r)
{
int rt=(++tot);
sum[rt]=0;
if(l<r)
{
int m=(l+r)>>1;
L[rt]=build(lson);
R[rt]=build(rson);
}
return rt;
}
int update(int pre, int l, int r, int x)
{
int rt=(++tot);
L[rt]=L[pre], R[rt]=R[pre], sum[rt]=sum[pre]+1;
if(l<r)
{
int m=(l+r)>>1;
if(x<=m)
L[rt]=update(L[pre], lson, x);
else
R[rt]=update(R[pre], rson, x);
}
return rt;
}
int query(int u, int v, int l, int r, int k)
{
if(l>=r)
return l;
int m=(l+r)>>1;
int num=sum[L[v]]-sum[L[u]];
if(num>=k)
return query(L[u], L[v], lson, k);
else
return query(R[u], R[v], rson, k-num);
}
int main()
{
tot=0;
int n, m;
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
Hash[i]=a[i];
}
sort(Hash+1, Hash+n+1);
int d=unique(Hash+1, Hash+n+1)-Hash-1;
T[0]=build(1, d);
for(int i=1; i<=n; i++)
{
int x=lower_bound(Hash+1, Hash+d+1, a[i])-Hash;
T[i]=update(T[i-1], 1, d, x);
}
while(m--)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
int x=query(T[l-1], T[r], 1, d, k);
printf("%d\n", Hash[x]);
}
}
另外附上挑战上归并树的解法:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cstdio>
using namespace std;
#define lson i<<1,l,m
#define rson i<<1|1,m+1,r
const int maxn = 100111;
const int maxm = 5111;
int a[maxn];
int nums[maxn],L[maxn],R[maxn],K[maxn];
vector<int>dat[maxn<<2];
void pushup(int i)
{
dat[i].resize(dat[i<<1].size()+dat[i<<1|1].size());
merge(dat[i<<1].begin(),dat[i<<1].end(),dat[i<<1|1].begin(),dat[i<<1|1].end(),dat[i].begin() );
}
void build(int i,int l,int r)
{
if(r==l)
{
dat[i].push_back(a[l]);
return;
}
int m = (l+r)>>1;
build(lson);
build(rson);
pushup(i);
}
int query(int ql,int qr,int x,int i,int l,int r)
{
if(ql>r || qr<l)
return 0;
if(ql<=l && qr>=r)
return upper_bound(dat[i].begin(),dat[i].end(),x)-dat[i].begin();
int m = (l+r)>>1;
int lc = query(ql,qr,x,lson);
int rc = query(ql,qr,x,rson);
return lc+rc;
}
int main()
{ int N,M;
cin>>N>>M;
for(int i=1;i<=N;i++)
scanf("%d",a+i),nums[i]=a[i];
build(1,1,N);
sort(nums+1,nums+1+N);
for(int i=0;i<M;i++)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int lb=0,ub=N+1,mid;
while(ub>lb+1)
{
mid=(lb+ub)>>1;
if(query(l,r,nums[mid],1,1,N)<k)lb=mid;
else ub=mid;
}
printf("%d\n",nums[ub]);
}
}