在某提高组模拟赛中出现了一道主席树的裸题.
标算乱七八糟,胡搞毛搞嘻嘻哈哈.(老子不听老子不听,标算就是主席树.)
所以我学习了一下主席树.
下面以主席树标准模板题静态区间第 k k k小为例讲解主席树的基本使用.
这应该是2004年欧洲的一个ACM里首先出现的题目.
在vjudge中有很多一样的题,可以多倍经验.
洛谷模板
前置知识
主席树是可持久化线段树,所以它是线段树的可持久化的版本.
首先你需要会权值线段树,就是对每一个值建一棵线段树,节点里存储这个值出现的范围.
如果用普通的建法直接就爆炸了,所以你需要动态开点.
什么是动态开点呢?说白了也很简单.
普通的线段树的两个儿子不是
x
<
<
1
x<<1
x<<1和
x
<
<
1
∣
1
x<<1|1
x<<1∣1吗?
动态开点的时候,我们要记录
l
s
[
x
]
ls[x]
ls[x]和
r
s
[
x
]
rs[x]
rs[x].
当我们建到一个点的时候,我们给它直接分配一个编号.
代码如下.
如果是以洛谷模板线段树1为例:
const int yuzu=2e5;
typedef int karen[yuzu<<5|13];
karen val,lazy,ls,rs; // 和普通的线段树不一样的是,ls和rs不是固定的,要开两个数组记录.
void build(int &rt,int l,int r) {
if (!rt) rt=++cnt; // cnt是当前记录到的节点编号.
if (l==r) val[rt]=a[l];
else {
int mid=l+r>>1;
build(ls[rt],l,mid);
build(rs[rt],mid+1,r);
push_up(rt);
} // 其他的部分都差不多.
}
现在你大概懂了.
接下来我们进入主席树的学习.
主席树在本题的思想
我们对每一个节点的前缀建一棵线段树,节点存储
l
,
r
l,r
l,r值域区间中在
1
→
i
1\to i
1→i有多少个数.
如果每次都这样建就炸了,所以不行.
但是我们发现
i
i
i和
i
−
1
i-1
i−1的两个点建的前缀线段树只有
l
o
g
n
logn
logn个点是不同的(每一次更新显然更新
l
o
g
n
logn
logn个节点,因为每一次对值域二分).
那么我们可以让第
i
i
i个点和第
i
−
1
i-1
i−1个点的线段树共用左儿子或者右儿子,这样每次新加的节点的个数就是
l
o
g
n
logn
logn的,所以空间复杂度就对了.
我们接下来写新建的线段树.
int udt(int pre,int l,int r,int k) { // 基于pre这个节点新建一棵树.
int rt=++cnt,mid=l+r>>1; // 开一个新节点
le[rt]=le[pre],ri[rt]=ri[pre],sum[rt]=sum[pre]+1;
/*先共用左儿子右儿子,存储值的个数多一个.*/
if (l^r) k<=mid?le[rt]=udt(le[pre],l,mid,k):ri[rt]=udt(ri[pre],mid+1,r,k);
/*看插入权值的位置决定是更新左孩子还是右孩子*/
return rt;
}
然后是询问,我们发现对于
l
−
1
,
r
l-1,r
l−1,r两棵线段树来说,这是前缀和建的线段树,大胆猜想,它们有可加减性!
我记得有线段树合并,那是线段树相加,这里线段树当然也可以相减!
那么可以写出询问函数.
int query(int u,int v,int l,int r,int k) { // 询问用的是二分的模式
if (l>=r) return l; // 如果l=r,答案就是l了.
int x=sum[le[v]]-sum[le[u]],mid=l+r>>1;
/*我们看看左半部分有多少个数较小.*/
return x>=k?query(le[u],le[v],l,mid,k):query(ri[u],ri[v],mid+1,r,k-x);
/*如果左半部分有大于等于k个数字,询问左半部分,否则询问右半部分.注意k询问右半部分的时候要减去x.*/
}
这样子就搞定了.
最后给出代码,谢谢大家.
#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rel register ll
#define rec register char
#define gc getchar
//#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<23,stdin),p1==p2)?-1:*p1++)
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
char buf[1<<23],*p1=buf,*p2=buf;
inline int read(){
int x=0,f=1;char c=gc();
for (;!isdigit(c);c=gc()) f^=c=='-';
for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
return f?x:-x;
}
template <typename mitsuha>
inline bool read(mitsuha &x){
x=0;int f=1;char c=gc();
for (;!isdigit(c)&&~c;c=gc()) f^=c=='-';
if (!~c) return 0;
for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
return x=f?x:-x,1;
}
template <typename mitsuha>
inline int write(mitsuha x){
if (!x) return 0&pc(48);
if (x<0) pc('-'),x=-x;
int bit[20],i,p=0;
for (;x;x/=10) bit[++p]=x%10;
for (i=p;i;--i) pc(bit[i]+48);
return 0;
}
inline char fuhao(){
char c=gc();
for (;isspace(c);c=gc());
return c;
}
}using namespace chtholly;
using namespace std;
const int yuzu=2e5;
typedef int fuko[yuzu|10];
typedef int karen[yuzu<<5];
fuko gen,a,b;
struct chairman_tree{
#define ls le[rt],l,mid
#define rs ri[rt],mid+1,r
karen le,ri,sum; int cnt;
void build(int &rt,int l,int r) {
if (!rt) rt=++cnt;
sum[rt]=0; int mid=l+r>>1;
if (l^r) build(ls),build(rs);
}
int udt(int pre,int l,int r,int k) {
int rt=++cnt,mid=l+r>>1;
le[rt]=le[pre],ri[rt]=ri[pre],sum[rt]=sum[pre]+1;
if (l^r) k<=mid?le[rt]=udt(le[pre],l,mid,k):ri[rt]=udt(ri[pre],mid+1,r,k);
return rt;
}
int query(int u,int v,int l,int r,int k) {
if (l>=r) return l;
int x=sum[le[v]]-sum[le[u]],mid=l+r>>1;
return x>=k?query(le[u],le[v],l,mid,k):query(ri[u],ri[v],mid+1,r,k-x);
}
}my_;
int main() {
int i,n,q;
read(n),read(q);
for (i=1;i<=n;++i) a[i]=b[i]=read();
sort(b+1,b+n+1);
int m=unique(b+1,b+n+1)-b-1;
my_.build(*gen,1,m);
for (i=1;i<=n;++i)
gen[i]=my_.udt(gen[i-1],1,m,lower_bound(b+1,b+m+1,a[i])-b);
for (;q--;) {
int l=read(),r=read(),v=read();
write(b[my_.query(gen[l-1],gen[r],1,m,v)]),pl;
}
}