洛谷:P3384 【模板】文艺平衡树(Splay)

原题地址:https://www.luogu.org/problemnew/show/P3391

题目简述

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1


思路

首先明白Splay比起线段树能多干什么:

  • 可以在一个有序序列中任意数后面动态插入一串数(不能比a后面一个数还大)
  • 可以删除一段区间

可能描述不是很清楚,具体看这里面给的论文链接:信息学竞赛相关优秀文章合集
或者直接看这里:运用伸展树解决数列维护问题.pdf
如果搞不懂左旋右旋是什么,可以先看信息学竞赛相关优秀文章合集里的AVL树介绍。
对于AVL树是一种为了防止树结构不够优导致深度过深时间复杂度退化,在保持二叉搜索树性质不变的前提下进行的一种变换。简单说就是把往一边沉的树弄的两边平衡些。
而在Splay中,将特定点旋转到一定位置可以进行提取区间等操作,同时各种旋转间接的使树基本平衡(是的,可以构造数据卡掉。Treap树对此表示同情)
下面两幅图应该有助于理解:
左旋(下面代码里的表达:把S往上转一次)→
右旋(下面代码里的表达:把E往上转一次)→
图片来源:http://blog.csdn.net/sun_tttt/article/details/65445754
(文章是介绍红黑树的但是这个左旋右旋操作二叉搜索树通用)
论文里讲的很详细~
具体到这道题,引用一下zcysky在题解里给出的解释:

Splay可以用来维护序列。这样的话是把Splay当作一棵区间树。
所谓区间树和权值树的区别,大概就是区间树每个节点代表的是一段区间(典型代表就是一般的线段树)
权值树好理解一点,就是每个点真的代表一个点。
至于翻转操作我们可以利用Splay的过程实现。详见代码。(Splay能维护序列反转也是它作为LCT的辅助树的条件之一)

作为模板题没什么好说的。这边文章主要记录板子用。感谢zcysky的板子。


代码

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,m;
int fa[N],ch[N][2],size[N],rev[N],rt;//fa[a]表示a的父亲
inline void pushup(int x)//维护节点大小
{
    size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
void pushdown(int x)//标记下传
{
    if(rev[x]){//是否翻转了区间
        swap(ch[x][0],ch[x][1]);
        rev[ch[x][0]]^=1;rev[ch[x][1]]^=1;rev[x]=0;
    }
}
void rotate(int x,int &k)//旋转
{
    int y=fa[x],z=fa[y],kind;
    if(ch[y][0]==x)
        kind=1;
    else
        kind=0;
    if(y==k)
        k=x;
    else {
        if(ch[z][0]==y)
            ch[z][0]=x;
        else
            ch[z][1]=x;
    }
    ch[y][kind^1]=ch[x][kind];
    fa[ch[y][kind^1]]=y;
    ch[x][kind]=y;
    fa[y]=x;
    fa[x]=z;
    pushup(x);
    pushup(y);
}
void splay(int x,int &k)//伸展操作,将x一直旋转直到x就是k
{
    while(x!=k){
        int y=fa[x],z=fa[y];
        if(y!=k){
            if((ch[y][0]==x)^(ch[z][0]==y))
                rotate(x,k);//该节点与父亲分别是他们爸的左孩子\右孩子或者是右孩子\左孩子旋转2次x
            else
                rotate(y,k);//该节点与父亲同是他们爸的左孩子或同是右孩子先旋转一次y再旋转一次x
        }
        rotate(x,k);
    }
}
void build(int l,int r,int f) //建立一颗完全平衡的二叉树
{
    if(l>r)
        return;
    int mid=(l+r)/2;
    if(mid<f)
        ch[f][0]=mid;
    else
        ch[f][1]=mid;
    fa[mid]=f;
    size[mid]=1;
    if(l==r)
        return;
    build(l,mid-1,mid);
    build(mid+1,r,mid);
    pushup(mid);
}
int find(int x,int k)//寻找以x为根的子树里第k大的
{
    pushdown(x);
    int s=size[ch[x][0]];
    if(k==s+1)
        return x;
    if(k<=s)
        return find(ch[x][0],k);
    else
        return find(ch[x][1],k-s-1);
}
void rever(int l,int r)//关于如何从Splay中提取区间请看上文思路中的论文
{
    int x=find(rt,l),y=find(rt,r+2);
    splay(x,rt);
    splay(y,ch[x][1]);
    int z=ch[y][0];
    rev[z]^=1;
}
int main()
{
    scanf("%d%d",&n,&m);
    rt=(n+3)/2;
    build(1,n+2,rt);//区间左右各多加1个数方便提取区间
    for(int i=1;i<=m;i++){
        int L,R;
        scanf("%d%d",&L,&R);
        rever(L,R);
    }
    for(int i=2;i<=n+1;i++)
        printf("%d ",find(rt,i)-1);
    return 0;
}

原文地址:https://www.cnblogs.com/yyy2015c01/p/8460561.html

时间: 2024-08-08 23:17:04

洛谷:P3384 【模板】文艺平衡树(Splay)的相关文章

洛谷 P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是 (1,2,?n?1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1≤l≤r≤n 输出格式: 输出一行n个数字,表示原始序列经过m次变换后的结果

树链剖分[模板](洛谷 P3384)

洛谷·[模板]树链剖分 写在前面 首先,在学树链剖分之前最好先把 LCA.树形DP.DFS序 这三个知识点学了 如果这三个知识点没掌握好的话,树链剖分难以理解也是当然的. 树链剖分 树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度 概念 dfs1() dfs2() 对剖过后的树建线段树 处理问题 概念 重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子 叶子节点没有重儿子

Tyvj P1729 文艺平衡树 Splay

题目: http://tyvj.cn/p/1729 P1729 文艺平衡树 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 此为平衡树系列第二道:文艺平衡树 描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入格式 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n)  m表示翻转操作次数

BZOJ3223: Tyvj 1729 文艺平衡树 [splay]

3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3595  Solved: 2029[Submit][Status][Discuss] Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 Input 第一行为n,m n表示初始序列有n个数,这个序列依次

洛谷3380 二逼平衡树(树套树)

题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 查询k在区间内的排名 查询区间内排名为k的值 修改某一位值上的数值 查询k在区间内的前驱(前驱定义为严格小于x,且最大的数,若不存在输出-2147483647) 查询k在区间内的后继(后继定义为严格大于x,且最小的数,若不存在输出2147483647) 注意上面两条要求和tyvj或者bzoj不一样,请注意 输入输出格式 输入格式: 第一行两个数 n,m 表示长度为n的有序序列和m个操作 第二行有n个数,

【C++】最近公共祖先LCA(Tarjan离线算法)&amp;&amp; 洛谷P3379LCA模板

1.前言 首先我们介绍的算法是LCA问题中的离线算法-Tarjan算法,该算法采用DFS+并查集,再看此算法之前首先你得知道并查集(尽管我相信你如果知道这个的话肯定是知道并查集的),Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解(个人认为). 2.思想 下面详细介绍一下Tarjan算法的思想: 1.任选一个点为根节点,从根节点开始. 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过. 3.若是v还有子节点,返回2,否则下一步. 4.合并v到u上. 5.寻找与当前点

AC自动机(附洛谷P3769模板题)

首先,介绍一下AC自动机(Aho-Corasick automaton),是一种在一个文本串中寻找每一个已给出的模式串的高效算法. 在学习AC自动机之前,你需要先学习Trie树和KMP算法,因为AC自动机正式利用并结合了两者的思想. 说到实际的不同,其实AC自动机只是在Trie树上引入了一个类似KMP中next数组的东西叫做Fail指针. 对于每一个节点,Fail指针指向该节点所代表的字符串中,次长的.在Trie树中存在的后缀(因为最长的在Trie树种存在的后缀就是其本身)所代表的节点. 举例:

洛谷P3375 [模板]KMP字符串匹配

To 洛谷.3375 KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了. 输入输出格式 输入格式: 第一行为一个字符串,即为s1(仅包含大写字母) 第二行为一个字符串,即为s2(仅包含大写字母) 输出格式: 若干行,每行包含一个整数,表示s2在s1中出现的位置 接下来1行,包括length(s2)个整

洛谷.3803.[模板]多项式乘法(FFT)

题目链接:洛谷.LOJ. FFT相关:快速傅里叶变换(FFT)详解.FFT总结.从多项式乘法到快速傅里叶变换. #include <cmath> #include <cctype> #include <cstdio> #include <algorithm> #define gc() getchar() const int N=1e6+5; const double PI=acos(-1); int n,m; struct Complex { double

洛谷.1919.[模板]A乘B Problem升级版(FFT)

题目链接:洛谷.BZOJ2179 //将乘数拆成 a0*10^n + a1*10^(n-1) + ... + a_n-1的形式 //可以发现多项式乘法就模拟了竖式乘法 所以用FFT即可 注意处理进位 //n位*n位最多就只有2n位了 //论putchar的速度..还是快的 #include <cmath> #include <cstdio> #include <cctype> #include <algorithm> #define gc() getchar