ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

文艺平衡树

2021-09-29 22:02:15  阅读:182  来源: 互联网

标签:标记 int tr 文艺 分裂 大小 平衡 节点


文艺平衡树


题目描述

洛谷P3391 文艺平衡树


核心思路

这题可以使用FHQ Treap来处理维护区间操作问题。如果不了解FHQ Treap,请移步这篇博客:传送门

那么FHQ Treap是如何处理维护区间操作问题呢?例如,要操作的区间为 [ l , r ] [l,r] [l,r],那么我们就在fhq Treap里把这一段直接split拆出来进行操作然后再合并回去即可。

这里是采用按大小分裂:把树拆成两棵树,其中一棵树的大小等于给定的大小,剩余部分在另外一颗树里

对于区间 [ l , r ] [l,r] [l,r]来说,其实就是:

  • 按大小 l − 1 l-1 l−1将root数分裂成x树和y树,那么根据按大小分裂原则可知,x树的大小其实就是 l − 1 l-1 l−1, y y y树的大小是 ≥ l \geq l ≥l的
  • 然后按大小 r − l + 1 r-l+1 r−l+1将上面得到的y树分裂成y树(为了以示区分,我们下面将这次分裂得到的y树记录为 y ′ y' y′树)和z树,那么根据按大小分裂原则可知,此时 y ′ y' y′树的大小就是我们需要操作的区间 [ l , r ] [l,r] [l,r]了,那么我们对 y ′ y' y′树进行操作就好了
  • 最后把x树,y树,z树合并回去就好了

知道了fhq Treap怎么处理区间问题,怎么来解决文艺平衡树问题呢?

我们需要使用线段树中常见的概念:懒标记。每当我们想要对一个区间进行翻转时,我们就对这个区间维护懒标记:如果原来没有懒标记,那就打上;如果原来有懒标记,那就去掉,因为翻转两次等于没有翻转嘛。这里可以使用异或运算来实现。

那么什么时候需要下传懒标记呢?如果当前结点的左右子树在后续代码中有被更改的风险的话,那就下传,以防万一

其实按大小分裂的FHQ Treap和按值分裂的FHQ Treap大体上是一样的


代码

#include<iostream>
#include<cstring>
#include<random>
#include<algorithm>
using namespace std;
const int N=1e5+10;
std::mt19937 rnd(233);
struct Node{
    int l,r;    //左孩子节点的编号  右孩子节点的编号 编号其实就是下标
    int val;    //该节点内的权值
    int key;    //该节点的随机索引 即随机优先级
    int size;   //以该节点为根的所形成的树的大小
    bool flag;  //标记该节点是否需要被翻转
}tr[N];
//idx给每个节点分配的编号(下标) 最后它记录的就是这棵树中节点的总个数
//root记录的就是这棵树的根节点
int idx,root;

//建立新节点  给该节点分配编号
int newnode(int val)
{
    tr[++idx].val=val;   //记录该节点的值为val
    tr[idx].key=rnd();   //随机生成该节点的优先级
    tr[idx].size=1;      //以该节点为根的树的大小为1
    return idx;          //返回分配给该节点的编号idx
}

//更新节点的信息  记录以该节点u为根的树的大小
void update(int u)
{
    tr[u].size=tr[tr[u].l].size+tr[tr[u].r].size+1;
}

//下放懒标记
void pushdown(int u)
{
    //要翻转整颗子树,要先把左右两个儿子翻转,
    //然后把懒标记继续下传给左右子树 就相当于递归翻转左右两棵子树
    swap(tr[u].l,tr[u].r);
    tr[tr[u].l].flag^=1;    //把懒标记下传给左子树
    tr[tr[u].r].flag^=1;    //把懒标记下传给右子树
    tr[u].flag=false;       //清空当前节点的懒标记
}

//按大小siz将u树分裂成x树和y树
//这里用 引用 将分裂得到的x树和y树 返回回去
void split(int u,int siz,int &x,int &y)
{
    //如果要分裂的当前这棵u树是不存在的 那么不可能分裂出x树和y树
    //所以把x和y都设为0
    if(!u)
        x=y=0;
    else
    {
        //有可能被更改 所以需要下放懒标记
        if(tr[u].flag)
            pushdown(u);
        //如果当前节点u的左子树的大小tr[tr[u].l].size比给定的siz小
        //由于有可能当前节点u的右子树中存在大小比给定的siz小 所以还需要对u的右子树递归分裂
        //注意对u的右子树递归分裂时 要把左子树的大小和节点u都减去 这样才是右子树的siz
        if(tr[tr[u].l].size<siz)
        {
            x=u;
            //按大小siz-tr[tr[u].l].size-1将tr[u].r右子树分裂成 tr[u].r树 和y树
            split(tr[u].r,siz-tr[tr[u].l].size-1,tr[u].r,y);
        }
        else
        {
            y=u;
            //按大小siz将tr[u].l左子树分裂成x树和tr[u].l树
            split(tr[u].l,siz,x,tr[u].l);
        }
        //分裂完了之后 要记得更新节点信息
        update(u);
    }
}

//将x树和y树合并 返回的结果是这两棵树合并后的那个根节点
int merge(int x,int y)
{
    //如果x树不存在y树存在 那么合并后就只有y树 因此返回y树
    //如果y树不存在x树存在 那么合并后就只有x树 因此返回x树
    if(!x||!y)
        return x+y;
    //这里当作是大根堆来处理 写成> 或者>=都可以
    //也可以当作小根堆来处理 即写成< <= 那么就是小根堆
    if(tr[x].key>tr[y].key)
    {
        //有可能被更改 所以需要下放懒标记
        if(tr[x].flag)
            pushdown(x);
        tr[x].r=merge(tr[x].r,y);
        update(x);
        return x;
    }
    else
    {
        //有可能被更改 所以需要下放懒标记
        if(tr[y].flag)
            pushdown(y);
        tr[y].l=merge(x,tr[y].l);
        update(y);
        return y;
    }
}

//将区间[l,r]内的数翻转
void reverse(int l,int r)
{
    int x,y,z;
    //先按大小l-1将root树分裂成x树和y树
    split(root,l-1,x,y);
    //再按大小r-l+1将y树分裂成y树和z树
    split(y,r-l+1,y,z);
    //此时y树就是我们需要翻转的区间
    tr[y].flag^=1;
    //最后把x树,y树,z树合并
    root=merge(merge(x,y),z);
}

//中序遍历
void inorder(int u)
{
    if(!u)  //递归边界
        return;
    //有可能被更改 所以需要下放懒标记
    if(tr[u].flag)
        pushdown(u);
    inorder(tr[u].l);   //左
    printf("%d ",tr[u].val);    //根
    inorder(tr[u].r);           //右
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        root=merge(root, newnode(i));   //合并更新根节点
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        reverse(l,r);   //将区间[l,r]内的数翻转
    }
    //将翻转后的序列输出
    inorder(root);
    return 0;
}

标签:标记,int,tr,文艺,分裂,大小,平衡,节点
来源: https://blog.csdn.net/qq_45832461/article/details/120557222

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有