主席树(可持久化权值线段树)模板学习笔记

碎碎念

其实主席树似乎是特指可持久化权值线段树,不知道能否指可持久化线段树呢///

主席树

洛谷模板

给定 \(n\) 个整数构成的序列 \(a\)\(m\)次询问对于指定的闭区间 \([l, r]\) 查询其区间内的第 \(k\)小值。

\(n,m<=2e5,|a_i|<=1e9\)

可持久化线段树,保存每次插入时的历史版本。动态开点,每次保存新开的根节点编号\(root[N]\)和每个左右儿子编号\(lson,rson\)

#define N 200005
#define lc(x) t[x].lc
#define rc(x) t[x].rc

int n,m,a[N];
vector<int> v;
struct seg{
	int lc,rc,sum;
}t[N*22];
int root[N],idx;

build()

建空树。

void build(int &x,int l,int r){//传引用赋值
	x=++idx;
	if(l==r) return;
	int m=l+r>>1;
    build(lc(x),l,m);
    build(rc(x),m+1,r);
}

insert()

建主席树,对每个版本依次插入,插入的基树建立在上一个版本上。

root[i]表示第i个版本的根节点编号,第i棵主席树实际上保存的是[1,i]区间所属的权值线段树。

void insert(int x,int &y,int l,int r,int v) 
    //y是目前要插入的版本的节点,x是上一个版本的同步节点。y的插入建立在x的基础上
    //v是目前要插入的权值
{
    y=++idx;t[y]=t[x];t[y].sum++;//先把y的左右儿子设置成与x相同,增加y区间权值的总数
    if(l==r) return;
    int m=l+r>>1;
    if(v<=m) insert(lc(x),lc(y),l,m,v);//双指针同步搜索传引用,此时会新建y的左子节点
    else insert(rc(x),rc(y),m+1,r,v);//此时会新建y的右子节点
    
}

//在主函数中
for(int i=1;i<=n;i++)
    	insert(root[i-1],root[i],1,n,a[i]);//在i-1版本的基础上插入i版本,权值为a[i]

query()

在主席树上查询[l,r]第k小。

[l,r]上的信息=[1,r]的信息-[1,l-1]的信息。

x为l-1版本指针,y为r版本指针。x,y同步搜索到达某区间,则[l,r]有多少个数字在此区间内=t[y].sum-t[x].sum。二分搜索找到第k个数字即可。

int query(int x,int y,int l,int r,int k)
{
	if(l==r) return l;
	int m=l+r>>1;
	int sum=t[lc(y)].sum-t[lc(x)].sum; //x、y的左儿子有多少个数字
	if(k<=sum) return query(lc(x),lc(y),l,m,k);
	else return query(rc(x),rc(y),m+1,r,k-sum);
}

//在主函数中
cin>>l>>r>>k;
query(root[l-1],root[r],1,n,k);

离散化

n小值域大,考虑将数值映射为下标,需要离散化与去重

//vector的作用
for(int i=1;i<=n;i++)
{
	cin>>a[i];v.push_back(a[i]);
}
//离散化
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
//unique将不相同的数字排到可变数组最前,并返回第一个重复数字的指针。erase将重复部分删掉。

//得到x的下标
int getid(int x)
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
	//lower_bound返回第一个>=x的指针。
	//1 1 2 3查询1,再减去v.begin()会得到0,所以要加上1
}

主函数

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];v.push_back(a[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	int vn=v.size();//1~vn即为离散化后的值域下标
	for(int i=1;i<=n;i++)
		insert(root[i-1],root[i],1,vn,getid(a[i]));//多次插入a[i]离散化后对应的下标即可
	
	while(m--)
	{
		int l,r,k;cin>>l>>r>>k;
		int id=query(root[l-1],root[r],1,vn,k)-1;
		cout<<v[id]<<'\n';
	}
}
posted @ 2022-10-02 21:26  Hssliu  阅读(32)  评论(0)    收藏  举报