可持久化线段树(主席树)
可持久化概念:
可持久化实质上就是存储该数据结构所有的历史状态,以达到高效的处理某些信息的目的。
问题:(第k小数)
题目链接:给定长度为N的序列A,有M次询问,求在[l,i]区间内第k小的数是多少。
做法:
由题意知道我们肯定要对区间进行排序,但是我们的排序不是每次询问才排序,是初始化就排序并离散化——针对数字较大但数据不大的情况下排序离散化完毕后,以离散化数组建主席树,设ii属于区间[1,n],对原数组的[1,i]区间的数做统计(例如下图,区间中按离散化数组顺序统计1的个数、2的个数、3的个数、4的个数、8的个数、9的个数),有序地插入节点到离散化数组的主席树中,记录好原数组每个节点对应的线段树起点,针对样例有几个示意图。注意,这里的橙色节点是新节点,与之前出现的那个图不一样。
[1,1]的情况:
[1,4]的情况:
我们按照上面的做法构建的主席树是为了方便我们查找第k小值。因为我们是以离散数组构建的主席树,那么从根节点出发,左子树部分的数必定不大于右子树部分的数。于是就可以将左儿子的节点个数x与k做比较,若 k≤x,则第kk小值一定在左子树里面,若x≤k,则第k小值一定在右子树里面,然后递归往下走,缩小范围。值得注意的是,前者递归时,k直接传下去即可,后者递归时,需要将k减去左子树的数的个数再传递这个k值。
例如我们查找[1,4]中第2小的值,图示如下,绿色节点为该值存在的区间位置。
需要注意的是,第二个绿色节点才是绿色根节点的左子树,因为左子树表示的区间是靠前的那一半。
我们发现可以用前缀和来维护:
只要用预处理大法分别以[1,l]和[1,r]的数建立权值线段树,每个点的值对位相减即可。
区间[l,r] 我们只要[1.r]-[1,l-1]即可。
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=10010;
struct node
{
int l ,r;//左右儿子节点
int cnt;//在区间范围内的数
}tr[20*N];
int a[N];
int root[N],idx;
vector<int>nums;//离散化数组
int n,m;
int find(int x)//离散化时新的下标
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
int build(int l,int r)//建树
{
int p=++idx;
if(l==r) return p;
int mid=l+r>>1;
tr[p].l=build(l,mid);
tr[p].r=build(mid+1,r);
return p;
}
//增加一个数 pre 为上一个的根节点。
int insert(int pre,int l,int r,int x)
{
int q = ++idx, mid = (l + r) >> 1;
tr[q].l = tr[pre].l, tr[q].r = tr[pre].r, tr[q].cnt = tr[pre].cnt + 1;
if(l < r){
//应该更新哪一个值域区间
if(x <= mid) tr[q].l = insert(tr[pre].l, l, mid, x);
else tr[q].r = insert(tr[pre].r, mid + 1, r, x);
}
return q;
}
//查询
int query(int x,int y,int l,int r,int k)
{ //找到了
if(l==r) return l;
else
{
int mid=l+r>>1;
//对位相减
int cnt=tr[tr[y].l].cnt-tr[tr[x].l].cnt;
if(k<=cnt) return query(tr[x].l,tr[y].l,l,mid,k);
else return query(tr[x].r,tr[y].r,mid+1,r,k-cnt);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
nums.push_back(a[i]);
}
//离散化
sort(nums.begin(), nums.end());//从小到大排序
nums.erase(unique(nums.begin(), nums.end()), nums.end());//去重
root[0] = build(0, nums.size() - 1);//建树初始化版本
for(int i=1;i<=n;i++)
{
root[i]=insert(root[i-1],0,nums.size()-1,find(a[i]));
}
while(m--)
{
int l,r,d;
cin>>l>>r>>d;
printf("%d\n",nums[query(root[l-1],root[r],0,nums.size()-1,d)]);
}
return 0;
}
treap(平衡树)
众所周知Treap = BST + heap
BST又是中序遍历 是从小到大的 左中右
说说这个BST,就是说一个根节点p,左儿子一定小于他,右儿子大于它。
也就是BST的中序遍历是严格单调递增的。
那么就可以进行一些操作了。
首先为了维护这个BST我们需要一个左旋zag和右旋zig,分别表示将根节点和左右儿子交换位置,使交换后还满足BST的性质。
他可以有几项操作
1 插入操作
1.1插入已有的值(就cnt++);
1.2插入没有的值
2删除操作
2.1删除中间节点(把中间节点转为叶子节点)
2.2删除叶子节点(直接删)
3找前驱后继(比某个数小的最大值/比某个数大的最小值)
前驱就是左边最大的,后继就是右边最小的
node
{
int l,r;左儿子编号 右儿子编号
int key,val;排序序号(BST,有序) 优先级(大根堆heap里的)
}tr[N]
root.val >= root.left.val
root.val >= root.right.val
val取随机,则树的结构就随机(高度的平均就接近logn)
加两个哨兵
-∞ +∞ 处理边界
旋转的样例
hh,tt分别在整个序列的最前和最后 在旋转前后没有变化
同时 中序遍历(递增序列)的结构在旋转前后都是 hh y z x tt
x y
/ \ 右旋zig / \
y tt -> hh x
/ \ <- / \
hh z 左旋zag z tt
右旋zig :即根节点向右旋转
void zig(int &p)
{
int q=tr[p].l;
tr[p].l=tr[q].r;
tr[q].r=p;
p=q;pushup(tr[p].r);
pushup(p);
}
左旋zag
void zag(int &p)
{
int q=tr[p].r;
tr[p].r=tr[q].l;
tr[q].l=p;
p=q;
pushup(tr[p].l);pushup(p);
}
题目代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, INF = 1e8;
int n;
struct Node
{
int l, r;
int key, val;//节点键值 || 权值
int cnt, size;//这个数出现次数 || 每个(节点)子树里数的个数
}tr[N];
int root, idx;
void pushup(int p)//儿子信息更新父节点信息
{
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
//左儿子树 数个数+ 右儿子树 数个数+ 当前节点数出现次数(重复次数)
}
int get_node(int key)
{
tr[++idx].key = key;
tr[idx].val = rand();
tr[idx].cnt = tr[idx].size = 1;//默认创建时都是叶子节点
return idx;
}
void zig(int &p)//右旋 p指针指向的根节点
{
int q = tr[p].l;//先把p的左儿子q存下来
tr[p].l = tr[q].r;//b从q的右儿子变为p的左儿子
tr[q].r = p;//最终q的右儿子是p指向的x
p = q;//p从指向原来根节点x变为指向新根节点q
pushup(tr[p].r),pushup(p);//先更新 右子树(x bc) 再更新根节点y
}
void zag(int &p)
{
int q = tr[p].r;//先把p的右儿子y(q)存下来
tr[p].r = tr[q].l;//b挂x(p)
tr[q].l = p;//p挂y的左儿子
p=q;//p维护指向根节点
pushup(tr[p].l),pushup(p);
}
void build()//建树
{
get_node(-INF),get_node(INF);
root = 1,tr[1].r = 2;//根节点1号点,1号点的右儿子2号点
//build的时候不要漏了维护树的结构
pushup(root);
if (tr[1].val < tr[2].val) zag(root);
}
void insert(int &p,int key)
{
if(!p) p = get_node(key);//如果到达叶子节点fa 并进一步递归tr[fa].l or tr[fa].r == 空 新增一个key叶子节点
else if(tr[p].key == key) tr[p].cnt++;//如果插入已有的值 cnt++
else if(tr[p].key>key) //插入新的值 判断插入左子树还是右子树 直到叶子节点
{//如果要插入的key比p的key小 递归tr[p]的左子树tr[p].l
insert(tr[p].l,key);
if(tr[tr[p].l].val > tr[p].val) zig(p);//如果左子树val大于root.val 就换上来
}
//维护val是根节点root.val > 子树 root.l.val or root.r.val
else
{//如果要插入的key比p的key大 递归tr[p]的右子树tr[p].r
insert(tr[p].r,key);
if(tr[tr[p].r].val > tr[p].val) zag(p);//如果右子树val大于root.val 就换上来
}
pushup(p);
}
void remove(int &p,int key)
{
if(!p) return;//如果删除的值不存在
if(tr[p].key == key)//如果有值
{
if(tr[p].cnt>1) tr[p].cnt--;//如果cnt>1 cnt--
else if(tr[p].l || tr[p].r)//否则判断是否叶子节点
{//如果不是叶子节点
if(!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val)//右旋把p放到右子树 递归到叶子节点删除
{//如果没有右子树 肯定可以右旋 || 左子树的.val > 右子树的val
zig(p);
remove(tr[p].r,key);
}
else//否则左旋把p放到左子树 递归到叶子节点删除
{
zag(p);
remove(tr[p].l,key);
}
}
else p=0;//如果是叶子节点 直接删除
}
else if(tr[p].key > key) remove(tr[p].l,key);//如果 要删除的中序顺序key>当前节点p的tr[p].key 去p左子树删
else remove(tr[p].r,key);
pushup(p);
}
int get_rank_by_key(int p,int key)//通过数值找排名 传p而不是传&p
{
if(!p) return 0;//如果p不存在 返回0
if(tr[p].key==key) return tr[tr[p].l].size+1;//如果key和当前节点key相等 排名=左子树数的大小+1(当前节点)
if(tr[p].key>key) return get_rank_by_key(tr[p].l,key);//漏了return
//说明要找的key比当前节点小 去当前节点左子树找
return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r,key);
//去右边找返回的是右边子树的排名,所以还得加上根节点root数的个数以及根节点左子树总数root.l.size
}
int get_key_by_rank(int p,int rank)//通过排名找数值 传p而不是传&p
{
if(!p) return INF;//p不存在 则返回不可能存在的排名
if(tr[tr[p].l].size >=rank) return get_key_by_rank(tr[p].l,rank);//如果左子树数的个数>rank,则去左子树找
if(tr[tr[p].l].size+tr[p].cnt>=rank) return tr[p].key;//左子树+当前节点个数>=rank,则就是当前节点
//左子树+当前节点个数<rank,则递归右子树,同时往下传的rank-左子树-当前节点个数
return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);//-tr[p].cnt
}
int get_prev(int p,int key)//找到严格小于key的数中的最大数 传p而不是传&p
{
if(!p)return -INF;
if(tr[p].key>=key) return get_prev(tr[p].l,key);//当前这个数>=key 则要找的数在当前节点的左子树里
return max(tr[p].key, get_prev(tr[p].r, key));
// if(tr[p].key<key) //当前节点<key max(当前节点(无右子树) || 当前节点的右子树中的最大值(有右子树))
}
int get_next(int p,int key)//找到严格大于key的数中的最小数 传p而不是传&p
{
if (!p) return INF;
if (tr[p].key <= key) return get_next(tr[p].r, key);
return min(tr[p].key, get_next(tr[p].l, key));
}
int main()
{
build();
scanf("%d",&n);
while(n--)
{
int op,x;
scanf("%d%d",&op,&x);
if(op==1) insert(root,x);
else if (op == 2) remove(root,x);
else if (op == 3) printf("%d\n", get_rank_by_key(root, x) - 1);
else if (op == 4) printf("%d\n", get_key_by_rank(root, x + 1));
else if (op == 5) printf("%d\n", get_prev(root, x));
else printf("%d\n", get_next(root, x));
}
return 0;
}