题解 P3369 【【模板】普通平衡树】

· · 题解

平衡树板子

其实这就是平衡树的模板,我们有两种做法,第一种就是直接手写平衡树FHQ,还有一种就是用stl,前者比较快,stl又分两种:vector和set(主要是multiset);这里我主要讲两种FHQ和vector(主要是set不好操作qwq)。

FHQ是最近几年才流行的,是一个学生(范浩强)的大牛发明出来的,它主要涉及到两种操作哦,第一种就是分裂(split),与合并(merge),而这道题还需要多加一个函数kth求排名为k的树;不做多解释了,代码上慢慢解释——————1.

#include<cstdio>
#include<algorithm>
using namespace std;
const int M=1e6+10;
int n,root,l,r,p,cnt;//(l,r,q分别表示左子树与右子树还有中间节点的根);
struct node
{
    int l,r;
    int val,key;//val记录值,key记录键值(key值随机化,这样程序更加运行的稳定)
    int size;//size记录当前树的节点数
}tree[M];
void update(int u)//size包括根自己
{
    tree[u].size=tree[tree[u].l].size+tree[tree[u].r].size+1;//更新节点数 
}
void split(int u,int x,int &l,int &r)//加个&是为了可以吧l与r同时传回去(这也不叫传,就叫记录好了);
{//分离操作 
    if(!u)
    {//到底了 
        l=r=0;
        return ;
    }
    if(tree[u].val<=x)
    {//确定左子树的根 
        l=u;
        split(tree[u].r,x,tree[u].r,r);//递归右子树 
    }
    if(tree[u].val>x)
    {//确定右子树的根 
        r=u;
        split(tree[u].l,x,l,tree[u].l);//递归左子树 
    }
    update(u);//更新当前的节点 
}
void adde(int x)
{//建立只有一个节点的树 
    cnt++;tree[cnt].size=1;
    tree[cnt].l=tree[cnt].r=0;
    tree[cnt].val=x;tree[cnt].key=rand();//精髓,随机化,防止数据卡一条链  
}
int merge(int l,int r)
{//合并操作(可以同左偏树一起理解)
    if(!l || !r)//找到叶子节点 
        return l+r;
    //维护键值的一个小根堆
    if(tree[l].key<=tree[r].key)
    {//l节点是父亲节点 
        tree[l].r=merge(tree[l].r,r);//其左儿子为原来的左儿子,只需更新右儿子 
        update(l);return l;
    }
    if(tree[l].key>tree[r].key)
    {//r节点是父亲节点 
        tree[r].l=merge(l,tree[r].l);//其右儿子为原来的右儿子,只需更新左儿子 
        update(r);return r;
    }
}
int kth(int u,int k)
{//求排名第k的数
    if(k<=tree[tree[u].l].size)//在左区间 
        return kth(tree[u].l,k);
    else if(k==tree[tree[u].l].size+1)//这个数为此区间的根 
            return u;
    else
    {
        k-=(tree[tree[u].l].size+1);//排名为k要减少左儿子的节点数 
        return kth(tree[u].r,k);//在右区间 
    }
}
int main()
{
    scanf("%d",&n);
    int opt,x;//opt为操作,x变量; 
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&opt,&x);
        if(opt==1)
        {//添加操作
            split(root,x,l,r);//先分离<=x和>x的两棵树 
            adde(x);//新建一颗只有一个节点的为x的树
            root=merge(merge(l,cnt),r);//在<=x的树里面添加这棵树并且合并之前分离的>x的树 
        }
        if(opt==2)
        {//删除操作(两次分离操作) 
            split(root,x,l,r);//分离<=x的树和>x的树 
            split(l,x-1,l,p);//分离<x的树和==x的树 
            p=merge(tree[p].l,tree[p].r);//删除==x的树中的其中一个x,只能删一个,不能删整棵树 ,左儿子和右儿子合并后根节点就没了
            root=merge(merge(l,p),r);//将<x的树,>x的树以及剩余的==x的树合并 
        }
        if(opt==3)
        {//查询数x的排名 
            split(root,x-1,l,r);//先分离<x的树和>=x的树 
            printf("%d\n",tree[l].size+1);//树x的排名即为<x的树中元素的个数+1,不能分离成<=x的树,因为x有可能不止一个 
            root=merge(l,r);//合并 
        }
        if(opt==4)
        {//找排名第k的数 
            printf("%d\n",tree[kth(root,x)].val);//kth返回的是此节点 
        }
        if(opt==5)
        {//求x的前驱 
            split(root,x-1,l,r);//先分离<x的树和>=x的树
            printf("%d\n",tree[kth(l,tree[l].size)].val);//<x的树中 排名第 此树元素个数(size) 的数即为此树的前驱,不能分离成<=x的树,理由同上 
            root=merge(l,r);//合并 
        }
        if(opt==6)
        {//求x的后继 
            split(root,x,l,r);//先分离<=x的树和>x的树 
            printf("%d\n",tree[kth(r,1)].val);//>x的树中排名第一的数即为此数的后继,不能分离成>x的树,理由同上 
            root=merge(l,r);//合并 
        }
    }
    return 0;
}

代码也就100多行,看不懂的地方就说吧,其实也不长,几个函数也很简单;

还有一种就是vector,他非常简单,就是几个操作,但跑的是真的慢,考试的话很容易被卡,不建议大家使用(这道题范围较小,2w以内vector就可以过(应该是这样)); 献出我丑陋的代码(这份我就不作出解释了(反正也不是我主要讲的),不懂就在网上搜一下vector的用法吧)

还是给你们说吧

头文件 #include<vector>

向量的定义:

vector<int> vec;//定义一个vec型的向量a

vector<int> vec(5); //定义一个初始大小为5的向量

vector<int> vec(5,1); //初始大小为5,值都为1的向量

因为vector是可以根据插入的值的数量自动增长的,所以初始定义的时候只需要按照第一种方法定义就可以了(一维)。 很多时候我们需要开一个二维数组:

vector<vector<int>> vec(100);

Vector的下标和数组一样从0开始的

vec.size(); //返回向量的实际大小

vec.begin(); //返回向量的开始指针的位置

vec.end(); //返回向量的结束指针的下一个位置

vec.push_back(x); //在对象末尾插入数据x

vec.pop_back(); //在对象末尾删除数据

vec.clear(); //清除对象中的所有数据

vec.at(i); //访问容器中第i个数的值(推荐)

vec[i]: //访问容器中第i个数的值(不推荐) v

在第i+1个数前面插入一个数x:

vec.insert(vec.begin()+i,x);

删除第i+1个数:

vec.erase(vec.begin()+i);

这才是vector的精髓所在,以上删除,插入操作都是logn的,因为vector下标是从0开始的,所以下标为i的数实际上就是第i+1个数

以上两个操作再集合两个STL函数可以1行代码实现一个平衡树的单点操作。

include<algorithm>

这个头文件中有很多很实用的库函数,其中三个在noip范围内经常被用到

lower_bound();

upper_bound();

unique();//判重

1….lower_bound(a+1,a+n+1,x);

二分查找有序表中第一个大于等于x的数的位置

仅适用于非降序的有序表,如果是非升序的有序表,则需要重载:

lower_bound(a+1,a+n+1,x,greater<int>());

返回有序表中第一个小于等于x的数的位置,仅适用于非升序的有序表

给大家一个例子

一个长度为n的有序不下降序列,给定m次查找,如果查找的数在序列中,输出在序列中第一次出现的位置,如果不在序列中,输出他在哪两个位置之间(保证查找的数肯定在序列两个端点之间)

伪代码 :

int t=lower_bound(a+1,a+n+1,x)-a;//查找第一个大于等于x的数的位置,返回值是地址 if(a[t]==x) //如果这个数==x

printf("%d\n",t);

else //否则就在两个数之间

printf("%d %d\n",t-1,t);

2…upper_bound(a+1,a+n+1,x);

二分查找有序表中第一个大于x的数的位置

仅适用于非降序的有序表,如果是非升序的有序表,则需要重载:

upper_bound(a+1,a+n+1,x,greater<int>());

返回有序表中第一个小于x的数的位置,仅适用于非升序的有序表

例子差不多(手动滑稽)

一个长度为n的有序不下降序列,给定m次查找,如果查找的数在序列中,输出在序列中最后一次出现的位置,如果不在序列中,输出他在哪两个位置之间(保证查找的数肯定在序列两个端点之间)

伪码子:

int t=upper_bound(a+1,a+n+1,x)-a;//查找第一个大于x的数的位置,返回值是地址

if(a[t-1]==x) //如果这个数的前一个数==x

printf("%d\n",t-1);

else //否则就在两个数之间

printf("%d %d\n",t-1,t);

3…unique(a+1,a+n+1):

STL的去重函数,他的时间复杂度和手动去重(先排序,后去重)一样,都是nlogn,但是他的原理和手动去重不一样,他是把重复的元素放到序列的末尾,序列的前k个数都是不重复的有效元素,所以输出的时候只需要输出有效长度就可以了。

就这样使用

int k=unique(a+1,a+n+1)-a;//得到有效长度

for(int i=1;i<=k;i++) //只输出有效长度

printf("%d ",a[i]);

讲的差不多了,接下来就是vector的表演时刻了!!

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=100050;
vector<int> vec;
int a,b,x,n;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a,&x);
        if(a==1)
        {
            vec.insert(lower_bound(vec.begin(),vec.end(),x),x); 
        }
        if(a==2)
        {
            vec.erase(lower_bound(vec.begin(),vec.end(),x)); 
        }
        if(a==3)
        {
            printf("%d\n",lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1);
        }
        if(a==4)
        {
            printf("%d\n",vec.at(x-1));
        }
        if(a==5)
        {
            printf("%d\n",vec.at(lower_bound(vec.begin(),vec.end(),x)-vec.begin()-1));
        }
        if(a==6)
        {
            printf("%d\n",vec.at(upper_bound(vec.begin(),vec.end(),x)-vec.begin()));
        }
    }
    return 0;
}

好像stl讲的更多了??无所谓

所以,这道题就讲完了,没搞懂的评论或者私信发给我吧,我可以帮你解答(趁我还没忘(手动滑稽))!!!!!