题目链接:https://vjudge.net/problem/HDU-2665
参考博客:http://blog.csdn.net/acdreamers/article/details/8656644
感觉网上的资料不是很好,讲得也模糊,模板也没有。
还是大致看一下原理,然后通过模板题及代码来学习比较有效。
原理的话大白书P397那一段介绍挺好的,后面的图也挺直观的。当然网上搜罗一下也挺不错。
模板题就这道了,代码找个清晰靠谱的参考一下就挺好。
个人理解
主席树就是一个可以保留历史版本的线段树,通过动态开点和连接的方式来减少空间开销。
可以保留历史版本直观上理解就是可以解决询问历史情况的问题。
扩展的理解和使用就是可以维护一系列的线段树,每颗线段树都是前一棵线段树加一个或多个变化得到的,线段树之间有某种顺序上的关系。
然后利用线段树之间的关系(很多时候是分别询问,然后做差),可以解决很多新的问题,可以优化时间和空间复杂度。
假设维护n个点,有n个操作,那么跟普通线段树相比,使用主席树不会增加时间复杂度,都是O(nlogn),而空间复杂度也只是从O(n)增加到O(nlogn)而已。
关于区间第K大的问题。
静态问题可以使用主席树。
缺点是只能解决静态的问题,优点是时空复杂度低。
动态问题可以使用树套树或整体二分。
树套树和主席树相比,优点是能解决动态问题,缺点是时空复杂度高,特别是空间复杂度高。
关于本题的话,就是主席树的扩展使用。
第i棵线段树可以查询前i个数,位于特定范围的数的个数。
版本更新就是插入第i+1个数,并得到新版本,同时保留历史版本。
最后如果询问区间[l,r]的第k大。
那就考虑T[r]和T[l-1]两颗线段树,两颗线段树维护的值一相减就得到[l,r]这个区间,位于特定范围的数的个数。(可以理解为得到了一个新的线段树,这颗线段树维护了区间[l,r]上位于特定范围的数的个数)我们就假装有那么一颗线段树,然后再递归找到第k大的数是多大。
讲的具体一点。
假装就有这么一颗线段树,维护了数轴上一段范围的数的个数。
我一开始在根,所以看到了所有数的个数。
把数轴分成两半,左子树维护者左半边,右子树维护着右半边。
但是我要找第k大,我要考虑往左子树或者右子树走。
如果左子树中数的个数大于等于k,那答案就肯定在左子树中,我就往左走。
否则左子树肯定有x个数,且x
直到到达了一个节点,只维护了一种数,那这个数就是答案了。
代码
#include
#include
#define m ((l+r)>>1)
using namespace std;
const int maxn = 100010;
int n,q;
int a[maxn];
int b[maxn];
int N;
//////////////////////////主席树
int rt[maxn],ls[maxn<<5],rs[maxn<<5],tree[maxn<<5],tot;
/*
void init(int n)
{
tot=0;
for(int i=0;i<=n;i++)
rt[i]=0;
}
*/
void build(int l,int r,int& now)
{
now=++tot;
tree[now]=0;
if(l==r) return;
build(l,m,ls[now]);
build(m+1,r,rs[now]);
}
void update(int last,int l,int r,int& now,int pos)
{
now=++tot;
ls[now]=ls[last];
rs[now]=rs[last];
tree[now]=tree[last]+1;
if(l==r) return;
if(pos<=m) update(ls[last],l,m,ls[now],pos);
else update(rs[last],m+1,r,rs[now],pos);
}
int qry(int a,int b,int k)
{
int t1=rt[a-1];
int t2=rt[b];
int l = 1;
int r = N;
while(l=k)
{
t1=ls[t1];
t2=ls[t2];
r=m;
}
else
{
t1=rs[t1];
t2=rs[t2];
l=m+1;
k-=num;
}
}
return l;
}
//////////////////////////主席树
void read()
{
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
b[i]=a[i];
}
}
void solve()
{
read();
sort(b+1,b+1+n);
N = unique(b+1,b+1+n)-b-1;
tot=0;
build(1,N,rt[0]);
for(int i=1;i<=n;i++)
update(rt[i-1],1,N,rt[i],lower_bound(b+1,b+1+N,a[i])-b);
int l,r,k;
while(q--)
{
scanf("%d %d %d",&l,&r,&k);
printf("%d\n",b[qry(l,r,k)]);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}