线段树废话
我太懒了所以直接引用一下
主席树又称函数式线段树,顾名思义,也就是通过函数来实现的线段树,至于为什么叫主席树,那是因为是fotile主席创建出来的这个数据结构
主席树最经典的应用就是在线求区间第k大。
运用前缀和的思想,我们把序列的每一个前缀都建一颗线段树。每一个节点存的是这个节点对应值出现的次数,所以查询 [l,r] 的时候只需要把 r 这棵树“减去” l−1 这棵树就行了。而我们只需知道他们的大小关系,因此预处理的时候先把它离散化一下。
但是如果把序列的每一个前缀都“真的”建树的话当然会MLE,而我们发现每次新加进去一个数最多只需要改变 log2 个节点的权值,其他的都不变。所以我们可以通过共用上一棵树的节点来减小空间开销。
差不多长这样(出处见右下角):
可以发现本来要新建7个点,通过共用之后只新建了3个点。这样一来,总空间就变成了 n(1+log2n) 了(1是因为要建一颗空树)。
查询的时候像平衡树一样,如果k小于右子树大小查询右子树第k大,否则查询左子树的第k-sum大。
代码(洛谷P3834):
#include
#include
#include
#include
#define N 200005
using namespace std;
struct tree{
//主席树的左右儿子编号并不是x*2和x*2+1
//sum存子树大小(即这个子树的总次数)
int ls,rs,sum;
}t[N*20];
int n,m,num,nd,rt[N*20],a[N],b[N];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int x=0,f=1; char ch=readc();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
while (isdigit(ch)) x=x*10+ch-48,ch=readc();
return x*f;
}
void ntlz(int &x,int l,int r){//建空树
t[x=++nd].sum=0;
if (l==r) return; int mid=l+r>>1;
ntlz(t[x].ls,l,mid),ntlz(t[x].rs,mid+1,r);
}
void build(int &x,int l,int r,int fa,int p){//建树
t[x=++nd].ls=t[fa].ls,t[x].rs=t[fa].rs;
t[x].sum=t[fa].sum+1;
if (l==r) return; int mid=l+r>>1;
if (p<=mid) build(t[x].ls,l,mid,t[fa].ls,p);
else build(t[x].rs,mid+1,r,t[fa].rs,p);
}
int srch(int p,int q,int l,int r,int k){//查询
if (l==r) return l;
int mid=l+r>>1,df=t[t[q].ls].sum-t[t[p].ls].sum;//直接相减
if (k<=df) return srch(t[p].ls,t[q].ls,l,mid,k);
else return srch(t[p].rs,t[q].rs,mid+1,r,k-df);
}
int main(){
n=_read(),m=_read();
for (int i=1;i<=n;i++) a[i]=b[i]=_read();
sort(b+1,b+n+1),num=unique(b+1,b+n+1)-(b+1);//离散
nd=0,ntlz(rt[0],1,num);
for (int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+num+1,a[i])-b;//a就是离散后的数组
for (int i=1;i<=n;i++) build(rt[i],1,num,rt[i-1],a[i]);//建树
while (m--){
int l=_read(),r=_read(),k=_read();
printf("%d\n",b[srch(rt[l-1],rt[r],1,num,k)]);
}
return 0;
}
其实就是主席树
看名称就知道是什么东西了。。。
看名称就知道是干嘛的了。。。
支持查询/修改某一历史版本的信息。
同主席树一样,对于每一次修改,不用重新建树,而是改变路径上的节点信息,共用其他节点。查询的话直接查就行了。
然后就差不多了。。。
以洛谷P3919为例
#include
#include
#include
#include
#define N 1000005
using namespace std;
struct tree{
int ls,rs,x;
}t[N*40];
int n,m,nd,num,rt[N],a[N];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int x=0,f=1; char ch=readc();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
while (isdigit(ch)) x=x*10+ch-48,ch=readc();
return x*f;
}
void build(int &x,int l,int r){//建树
int mid=l+r>>1; x=++nd;
if (l==r) { t[x].x=a[l]; return; };
build(t[x].ls,l,mid),build(t[x].rs,mid+1,r);
}
void nsrt(int &x,int l,int r,int p,int w,int fa){//修改
t[x=++nd].ls=t[fa].ls,t[x].rs=t[fa].rs;//共用节点
if (l==r) { t[x].x=w; return; } int mid=l+r>>1;
if (p<=mid) nsrt(t[x].ls,l,mid,p,w,t[fa].ls);
else nsrt(t[x].rs,mid+1,r,p,w,t[fa].rs);
}
int srch(int &x,int l,int r,int p,int fa){//查询
t[x=++nd].ls=t[fa].ls,t[x].rs=t[fa].rs;//这道题要求两个操作都新增一个版本
if (l==r) return t[x].x=t[fa].x; int mid=l+r>>1;
if (p<=mid) return srch(t[x].ls,l,mid,p,t[fa].ls);
else return srch(t[x].rs,mid+1,r,p,t[fa].rs);
}
int main(){
n=_read(),m=_read();
for (int i=1;i<=n;i++) a[i]=_read();
build(rt[0],1,n);
while (m--){
int v=_read(),f=_read(),p=_read(),w;
if (f==1) nsrt(rt[++num],1,n,p,w=_read(),rt[v]);
else printf("%d\n",srch(rt[++num],1,n,p,rt[v]));
}
return 0;
}