主席树


在某提高组模拟赛中出现了一道主席树的裸题.
标算乱七八糟,胡搞毛搞嘻嘻哈哈.(老子不听老子不听,标算就是主席树.)
所以我学习了一下主席树.
下面以主席树标准模板题静态区间第 k k k小为例讲解主席树的基本使用.
这应该是2004年欧洲的一个ACM里首先出现的题目.
在vjudge中有很多一样的题,可以多倍经验.
洛谷模板

前置知识

主席树是可持久化线段树,所以它是线段树的可持久化的版本.
首先你需要会权值线段树,就是对每一个值建一棵线段树,节点里存储这个值出现的范围.
如果用普通的建法直接就爆炸了,所以你需要动态开点.
什么是动态开点呢?说白了也很简单.
普通的线段树的两个儿子不是 x &lt; &lt; 1 x&lt;&lt;1 x<<1 x &lt; &lt; 1 ∣ 1 x&lt;&lt;1|1 x<<11吗?
动态开点的时候,我们要记录 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 1i有多少个数.
如果每次都这样建就炸了,所以不行.
但是我们发现 i i i i − 1 i-1 i1的两个点建的前缀线段树只有 l o g n logn logn个点是不同的(每一次更新显然更新 l o g n logn logn个节点,因为每一次对值域二分).
那么我们可以让第 i i i个点和第 i − 1 i-1 i1个点的线段树共用左儿子或者右儿子,这样每次新加的节点的个数就是 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 l1,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;
  }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值