版权声明:装作自己有版权 https://blog.csdn.net/weixin_44574444/article/details/87202657
主席树模版
(传送门)POJ
洛谷也有题目:洛谷链接
题目描述:
建议在写主席树时,先要将权值线段数弄明白。
主席树就是一个具有历史意义的 我理解为有多棵线段数 一颗持久化的线段数,每次可持久化指的是它保存了这棵树的所有历史版本,最简单的办法是:如果你输入了n个数,那么每输入一个数字a[i],就构造一棵保存了从a[1]到a[i]的权值线段树。之所以这么做,是因为我们可以把第j棵树和第(i-1)棵树上的每个点的权值相减,来得到一颗新的权值线段树,而这个新的权值线段树相当于是输入了a[i]到a[j]以后得到的。
每一次我们存入一个数时,我们就重新构造出一棵新的线段树,并且我们存下了这棵权值线段数(为了方便找值)。
这里引用一位dalao的图片
我先来模拟一遍数据吧:
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
我们将1插入树中:
我们将每次经历过的子节点的cnt值++(就是权值线段数)
将5放入创建新的树:
我们按照此方法最后插入4是如下情况(其中每个节点的值代表的是某个范围内的数的个数)
不同于普通线段树的是主席树的左右子树节点编号并不能够用计算得到,所以我们需要记录下来,但是对应的区间还是没问题的。
这一段的核心代码如下:
void update(int l,int r,int &x,int y,int k)
{
T[++cnt]=T[y],T[cnt].sum++,x=cnt;
if(l==r) return;
int mid=(l+r)/2;
if(mid>=k) update(l,mid,T[x].l,T[y].l,k);//在左边就插入左儿子
else update(mid+1,r,T[x].r,T[y].r,k);//在右边就插入右儿子
}
下面是完整代码:
#include<bits/stdc++.h>
#include<vector>
using namespace std;
const int maxn=1e6+10;
int n,m,x,y,k,cnt=0;
int a[maxn],root[maxn];
vector<int> v;
int getid(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;//用STL中的lower_bound找到第一个大于x的数的坐标
}
struct node{
int l,r,sum;
}T[maxn*40];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9' || ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){x=10*x+ch-'0';ch=getchar();}
return x*f;
}
void init()
{
freopen("input.txt","r",stdin);
}
void update(int l,int r,int &x,int y,int k)//修改
{
T[++cnt]=T[y],T[cnt].sum++,x=cnt;//每次插入一个新的数字,将cnt记号++
if(l==r) return;
int mid=(l+r)/2;
if(mid>=k) update(l,mid,T[x].l,T[y].l,k);
else update(mid+1,r,T[x].r,T[y].r,k);
}
int query(int l,int r,int x,int y,int k)//查询操作
{
if(l==r) return l;
int mid=(l+r)/2;
int sum=T[T[y].l].sum-T[T[x].l].sum;//根据于好像是减法原理的东东,解决方案就是将主席树[1,r][1,r]减去主席树[1,l-1][1,l−1]就行了。其实这个原因并不难想,首先看到主席树的底层,全部是对数的统计。当主席树[1,r][1,r]减去主席树[1,l-1][1,l−1]时,统计也跟着减了,也就是说,现在统计记录的是[l,r][l,r]区间。
if(sum>=k) return query(l,mid,T[x].l,T[y].l,k);
else return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
void readdata()
{
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read(),v.push_back(a[i]);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());//我们对它进行离散化,节省空间,针对数字较大但数据不大的情况下
for(int i=1;i<=n;i++)
update(1,n,root[i],root[i-1],getid(a[i]));
for(int i=1;i<=m;i++)
{
x=read(),y=read(),k=read();
printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);
}
}
void work()
{
}
int main()
{
// init();
readdata();
work();
return 0;
}