关于平衡线段树的一点研究

平衡树可以维护带插入删除的序列操作,加上一些线段树的特性即可像线段树一样维护区间信息

同样,线段树可以维护区间信息,加上一些平衡树的特性也就可以实现插入删除

不知为何似乎没见过什么相关资料所以就自己yy了一下

考虑旋转:

可以发现每次旋转只需更新一个区间的信息

定义每个节点的size为子树中叶节点个数

考虑类似sbt的平衡规则,每个节点是平衡的,当且仅当其子节点的size不小于另一子节点的任一子节点的size

类似线段树,每个叶节点维护原序列,非叶结点维护区间

对于在第x个元素前插入元素,首先找到代表x的叶节点,将x换为一棵size=2的子树,其中右子节点为原来的x,左子节点为新插入元素

修改操作和区间查询类似线段树

删除操作很简单,因为删的是叶节点,可以将待删除节点和其父节点删去,兄弟节点补到原位置

每次插入/删除后要沿操作路径调整平衡以保证单次操作严格O(logn)

在以上几个操作中平衡线段树的常数明显小于splay,查询操作的常数与线段树没有明显差别,并且必要时可以支持严格复杂度的可持久化

当然类似替罪羊树/treap的平衡规则也是可以用的,但复杂度变为均摊/期望

splay的旋转规则不易推广至平衡线段树

于是有了裸题 bzoj1014 [JSOI2008]火星人prefix

#include<cstdio>
#include<cstring>
typedef unsigned long long u64;
const int N=600050,P=53;
int lc[N],rc[N],sz[N],p=1;
u64 v[N],pp[N];
char str[N],op[64],ch[64];
inline void up(int w){
    v[w]=v[lc[w]]*pp[sz[rc[w]]]+v[rc[w]];
}
inline void lrot(int&w){
    int u=lc[w];
    lc[w]=rc[u];
    rc[u]=w;
    v[u]=v[w];
    sz[u]=sz[w];
    up(w);
    sz[w]=sz[lc[w]]+sz[rc[w]];
    w=u;
}
inline void rrot(int&w){
    int u=rc[w];
    rc[w]=lc[u];
    lc[u]=w;
    v[u]=v[w];
    sz[u]=sz[w];
    up(w);
    sz[w]=sz[lc[w]]+sz[rc[w]];
    w=u;
}
inline void chk_l(int&w){
    int m=sz[rc[w]],l=sz[lc[lc[w]]],r=sz[rc[lc[w]]];
    if(m<l)lrot(w);
    else if(m<r)rrot(lc[w]),lrot(w);
}
inline void chk_r(int&w){
    int m=sz[lc[w]],l=sz[lc[rc[w]]],r=sz[rc[rc[w]]];
    if(m<r)rrot(w);
    else if(m<l)lrot(rc[w]),rrot(w);
}
void ins(int&w,int k){
    ++sz[w];
    if(sz[w]==2){
        lc[w]=++p;
        v[p]=ch[0]-‘a‘+1;
        sz[p]=1;
        rc[w]=++p;
        v[p]=v[w];
        sz[p]=1;
        up(w);
    }else if(k<sz[lc[w]]){
        ins(lc[w],k);
        up(w);
        chk_l(w);
    }else{
        ins(rc[w],k-sz[lc[w]]);
        up(w);
        chk_r(w);
    }
}
void chg(int&w,int k){
    if(sz[w]==1){
        v[w]=ch[0]-‘a‘+1;
        return;
    }
    if(k<=sz[lc[w]])chg(lc[w],k);
    else chg(rc[w],k-sz[lc[w]]);
    up(w);
}
int l,r;
u64 ans=0;
void get(int w,int L,int R){
    if(l<=L&&R<=r){
        ans=ans*pp[sz[w]]+v[w];
        return;
    }
    int M=L+sz[lc[w]];
    if(l<M)get(lc[w],L,M-1);
    if(r>=M)get(rc[w],M,R);
}
int build(int L,int R){
    int w=++p;
    if(L==R){
        v[w]=str[L]-‘a‘+1;
        sz[w]=1;
        return w;
    }
    int M=L+R>>1;
    sz[w]+=sz[lc[w]=build(L,M)];
    sz[w]+=sz[rc[w]=build(M+1,R)];
    up(w);
    return w;
}
inline int input(){
    int x=0,c=getchar();
    while(c>57||c<48)c=getchar();
    while(c>47&&c<58)x=x*10+c-48,c=getchar();
    return x;
}
inline int getc(){
    int c=getchar();
    while(c<=32)c=getchar();
    return c;
}
int main(){
    pp[0]=1;
    for(int i=1;i<100050;i++)pp[i]=pp[i-1]*P;
    scanf("%s",str+1);
    int n=strlen(str+1)+1,q,x,y;
    int rt=build(1,n);
    q=input();
    while(q--){
        op[0]=getc();
        if(op[0]==‘Q‘){
            x=input();y=input();
            ans=0;
            int L=0,R=n-(x>y?x:y);
            while(L<R){
                int M=L+R+1>>1;
                ans=0;
                l=x,r=x+M-1;
                if(l<=r)get(rt,1,n);
                u64 a1=ans;
                ans=0;
                l=y,r=y+M-1;
                if(l<=r)get(rt,1,n);
                if(a1!=ans)R=M-1;
                else L=M;
            }
            printf("%d\n",L);
        }else if(op[0]==‘R‘){
            x=input();ch[0]=getc();
            chg(rt,x);
        }else{
            x=input();ch[0]=getc();
            ins(rt,x);
            ++n;
        }
    }
    return 0;
}

显示代码

时间: 2024-10-13 22:44:39

关于平衡线段树的一点研究的相关文章

树秀于林风必摧之——线段树

关于线段树,其实我一开始也是很懵的,但看久了也就习惯了. 以下是我对线段树的一点理解,写得不好,也请各位看官见谅. 搜狗定义:线段树(Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 定义还是很显然的. 那么线段树都能做些什么呢? 在n个数中有m个询问,询问如下: 1.把q这个数改成v  O(logn); 2.求在1~n这个区间的和. 接下来我们讲原理(当然原理也是我自己的理解,可能不是正解,但我想,线段树这个东西大概就是这样的吧

hdoj 1556 Color the ball(线段树||树状数组)

Color the ball Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 12209    Accepted Submission(s): 6094 Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"

【线段树】【FeyatCup】——2.法法塔的奖励

首先感谢并膜拜两位大佬 wyl8899 & 法法塔 感谢两位的出题! 题目: 法法塔是很喜欢写程序的.所以冒着就算码农屌丝一辈子的风险也大无畏地写着程序.码农们为了表彰法法塔的坚持与执着,决定给法法塔颁布奖励,为了体现码农的屌丝气质,奖励也将由法法塔自己做出选择!所有的奖励被组织成了一棵树的形态,每个点都有一个权值.法法塔首先选择一个子树,然后选择从该子树内的一个叶子节点到该子树的根的一条路径,将路径上节点的权值依次排成一个序列,求得这个序列的最长不下降子序列长度,这个长度就是他能得到的奖品数目

CSU 1453: 平衡序列 学会线段树后必做

http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1453. 题目:给定一个大小为100000的数组,里面的数字最大也是100000.现在叫你求出一段子序列,使得他们任意两个数差的绝对值都不能超过k 其实这题的关键是数字的范围,不超过100000,这样的话 ,就可以用线段树整段覆盖了.记dp[i]为以这个数字为结尾的,最长的LIS的多少,开始的时候dp[i]=0,用线段树把他覆盖了.每次插入一个数a[i]的时候,都去找[a[i]-k,a[i]+k]

CF 474/F, 线段树 + 一点数学

啊回家真是颓,一周了什么都没做 题目大意:给出一坨数,每次询问区间当中有多少个数能把其他区间内的所有数整除 解:由于我是知道这个是线段树专题,所以一开始就奔着gcd去了,想了一下还真是gcd,因为如果一个数a能整除另一个数b,那么gcd(a,b)一定为a,所以这说明可以做区间加法,直接上线段树就是,询问个数的时候还傻叉地想了一会,后面发现给每个线段记一下当前线段表示的gcd有几个就行,合并直接合并(可以证明现在这个线段的gcd一定是作为最优的取值,因为没有数等于这个gcd说明没有数能整除当前区间

(转)线段树的区间更新

原文地址:http://blog.csdn.net/zip_fan/article/details/46775633 写的很好,昨天刚刚开始写线段树,有些地方还不是很明白,看了这篇博文,学会了数组形式保存线段树,还学会了区间更新 以下为转载的博文内容 距离第一次接触线段树已经一年多了,再次参加ACM暑假集训,这一次轮到我们这些老家伙们给学弟学妹们讲解线段树了,所以就自己重新把自己做过的题目看了一遍,然后写篇博客纪念一下.作为一个菜鸟,文中肯定有很多表达不是很准确甚至错误的地方,欢迎各位大牛指正.

BZOJ 3065 带插入区间K小值 替罪羊树套线段树

题目大意:带插入,单点修改的区间k小值在线查询. 思路:本年度做过最酸爽的题. 树套树的本质是一个外层不会动的树来套一个内层会动(或不会动)的树.两个树的时间复杂度相乘也就是差不多O(nlog^2n)左右.但是众所周知,高级数据结构经常会伴有庞大的常数,所以一般来说树套树的常数也不会小到哪去.所以在做这种题的时候先不要考虑常数的问题... 为什么要用替罪羊树呢?因为一般的平衡树都是会动的,这就很难办了.外层的树动了之后,内层的树肯定也是会动的.很显然,一般的二叉平衡树会经常会旋转,这样在动外层的

HDU5152 线段树 + 数论

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5152 ,线段树区间更新 + 点更新 + 数论知识(数论是重点QAQ),好题值得一做. BestCoder Round #24的C题,一道神题,不得不说,出题人的数论学的很好,很多人都没想到2333333不是素数的问题,当时全场爆零.我今天下午开始研究这道题,后来看了好久的标程才懂,惭愧. 一共有两个操作一个询问:1.询问[l , r]区间里的值的和; 2.将点x的值a[x]赋为2a[x]; 3.将区

线段树第二弹(区间更新)

上篇文章,我们介绍了线段树的基本概念和单点更新.区间查询,今天,我们来接着上次的线段树问题继续深入研究.在解决线段树问题的过程中,我们会遇到要求修改区间中某一元素值的问题,当然也可能会遇到要求修改一段子区间所有值的问题--即区间更新问题.回忆一下上篇文章单点更新的方法是,由叶节点逐级向上进行更新,此时更新一个节点值的时间复杂度为o(log n),(点击链接了解详情:线段树+RMQ问题第二弹),那么以这样的处理效率来进行区间更新结果会怎样?现在假设待更新区间数据的规模为 n ,那么就需要进行 n