bzoj1208 splay伸展树

本文介绍Splay树的基本操作,包括插入、查找、删除等,并详细解释如何通过Splay树实现区间操作,例如区间插入和删除。同时,文章还提供了一个完整的Splay树实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

splay伸展树主要有两种操作形式
(1)正常的二叉树插入形式
功能:a、查找 b、求最大值 c、最小值 d、求前驱 e、求后继 f、删点 g、合并splay树
(这里的删除直接利用splay树的结点下标)
(2)区间形式 (插入是以区间形式插入的)
区间形式的伸展树相当于线段树,支持线段树的所有操作,并且还支持区间插入这个功能,
比如操作区间[a,b],将根设为a-1,根的右孩子设为b+1,那么根的右孩子的左孩子就是所求区间
某个点插入区间也是一个道理
需要注意的是,这里init()自动生成了左右区间,方便之后的操作。注意实际有效的区间范围即可。
(区间删除或者说其他操作,利用的是原序列的下标,再通过函数找到对应的splay结点下标,转换到区间,再对区间删除)

这道题主要是二叉树的插入形式
需要用到 插入 找前驱 找后继 删点 合并 这几个功能
删点就是将所需要删除的点splay到根,去掉所有联系(即将左右结点的父亲结点设为0),然后合并左右子树即可。然后将root设为新合并的树的根即可
合并过程是将左子树的最大键值结点提为根,右子树插入左子树的根即可。 当左子数没有的时候,合并的结果直接是右子树。。

标记一下当前树是动物或者人即可用一棵splay树来完成

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <math.h>
#include <vector>
#include <cstdio>
#include <string>
#include<string.h>
#include <fstream>
#include <iostream>
#include <algorithm>
using namespace std;
#define exp 1e-8
#define INF 0x3f3f3f3f
#define ll long long
#define set(a,b) memset(a,b,sizeof(a));
#define set(a,b) memset(a,b,sizeof(a));
#define for1(a,b) for(int a=1;a<=b;a++)//1---(b)
#define for0(a,b) for(int a=0;a<=b;a++)//0---(b)
void bug(string st="bug")
{cout<<st<<endl;}
template<typename __ll>
inline void READ(__ll &m){
    __ll x=0,f=1;char ch=getchar();
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    m=x*f;
}
template<typename __ll>
inline void read(__ll &m){READ(m);}
template<typename __ll>
inline void read(__ll &m,__ll &a){READ(m);READ(a);}
template<typename __ll>
inline void read(__ll &m,__ll &a,__ll &b){READ(m);READ(a);READ(b);}
const int MAXN=100000+1000;
#define dat int
#define Key_value ch[ch[root][1]][0]
struct splaytree
{
    int ch[MAXN][2];  //孩子
    int pre[MAXN];   //父亲
    dat key[MAXN];  //键值
    int size[MAXN];  //大小
    int root,tot1;  //根  结点大小
    int s[MAXN],tot2;  //删除用的数组,记录删除了那些点,重复利用删除的点
    int kind,ans;
    void addnode(int &r,int father,dat k)
    {
        if(tot2) r=s[tot2--];
        else r=++tot1;
        ch[r][0]=ch[r][1]=0;
        pre[r]=father;
        key[r]=k;
        size[r]=1;
    }
    void push_up(int r)
    {
        if(!r)return ;  //添加这句,以防对0结点不小心操作
       int lson=ch[r][0],rson=ch[r][1];
       size[r]=1+size[lson]+size[rson];
    }
    void init()
    {
        kind=-1,ans=0;
        root=tot1=tot2=0;
        ch[0][0]=ch[0][1]=pre[0]=size[0]=0;
        key[0]=-INF;
    }
    void rotate(int x,int kind)
    {
        int y=pre[x];
        int z=pre[y];
        ch[y][!kind]=ch[x][kind];
        pre[ch[x][kind]]=y;
        if(z)   ch[z][ch[z][1]==y] = x;
        pre[x]=pre[y];
        ch[x][kind]=y;
        pre[y]=x;
        push_up(y);
    }
    void splay(int r,int goal)  //r是splay的结点下标
    {
        while(pre[r]!=goal)    //各类push_down已经在rotate中执行
        {
            if(pre[pre[r]]==goal)
            {
                rotate(r,ch[pre[r]][0]==r);
                continue;
            }
            int y=pre[r];
            int z=pre[y];
            int kind=  ch[z][0]==y;
            if(ch[y][kind]==r)
                rotate(r,!kind),rotate(r,kind);
            else rotate(y,kind),rotate(r,kind);
        }
        push_up(r);
        if(goal==0)  root=r;
    }
    int insert(int &x,int f,int val)  //返回插入结点的下标
    {
        if(x==0)
        {
            addnode(x,f,val);
            int tmp=x;
            splay(tmp,0);
            return tmp;  //如果是这样子插入的话,
        }                //由于是int &x,splay会将ch[pre[x]]][]修改,导致x也改变了
        if(val==key[x])
            return x;
        if(val<key[x])
             return insert(ch[x][0],x,val);
        else
            return insert(ch[x][1],x,val);
    }
    void erase(int r)    //重复利用删除的结点
    {
        if(!r)return ;
        s[++tot2]=r;
    }
    int get_next()    //返回后缀的结点下标
    {
        int r=root;
        r=ch[r][1];  //开始使劲靠左找后继
        while(ch[r][0])
            r=ch[r][0];
        return r;
    }
    int get_pre()  //返回前缀的结点下标
    {
        int r=root;
        r=ch[r][0];
        while(ch[r][1])
            r=ch[r][1];
        return r;
    }
    int get_max(int r)   //获得当前树最大键值的结点下标
    {
        while(ch[r][1])
            r=ch[r][1];
        return r;
    }
    void Union(int lroot,int rroot)  //合并两课树
    {
        if(lroot==0)  //左子树没有,root直接等于右子树
        {
            root=rroot;
            return ;
        }
        int r=get_max(lroot); //获得最大的左子树的最大的某一个点
        splay(r,0);   //并将其设为根
        ch[root][1]=rroot;  //合并
        pre[rroot]=root;
        push_up(root);
    }
    void cut_root(int idx)  //砍掉某一个结点,
    {
        splay(idx,0);
        int r=root;
        int lroot=ch[r][0];
        int rroot=ch[r][1];
        pre[lroot]=pre[rroot]=0;  //去掉联系
        erase(r);
        Union(lroot,rroot);
    }
    int find(int val,int r)  //查找,如果找到返回结点下标
    {
        if(!r) return 0;
        if(val==key[r])
            return r;
        if(val<key[r])
            return find(val,ch[r][0]);
        return find(val,ch[r][1]);
    }
    void solve(int val)
    {
        int r=find(val,root);
        if(!r)  //没找到同一个值的宠物 人
        {
            r=insert(root,0,val);
            splay(r,0);
            int preidx=get_pre();
            int nextidx=get_next();
            if((preidx!=0&&nextidx==0)||(preidx!=0&&nextidx!=0&&abs(key[preidx]-key[r])<=abs(key[nextidx]-key[r])))
            {
                ans+=abs(key[preidx]-key[r]);
                cut_root(preidx);
            }
            else
            {
                ans+=abs(key[nextidx]-key[r]);
                cut_root(nextidx);
            }
            push_up(root);
        }
        ans%=1000000;
        cut_root(r);
        push_up(root);
        if(size[root]==0)
            kind=-1;
    }
}t;
int main()
{

    int n,a,b,ans;
    read(n);
    t.init();
    while(n--)
    {
        read(a,b);
        if(t.kind==-1||t.kind==a)
            t.insert(t.root,0,b),t.kind=a;
        else
            t.solve(b);
    }
    printf("%d\n",t.ans);
    return 0;
}
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值