后缀平衡树模板(例题SPOJ - DISUBSTR)

17 篇文章 1 订阅
13 篇文章 0 订阅

删除操作还没有验证,原本例题是HYSBZ5084,不过网站好像炸了,一直没法提交,目前不知道删除代码正确性,不过插入和维护height都已经验证过

意思就是你可能会被这篇文章演

平衡树用treap写的


什么是后缀平衡树?

利用平衡树动态维护后缀数组。

可以支持的操作为加入一个字符到串末尾或者删除末尾一个字符。

先搞清两个问题:

只能在末尾操作吗?

中间插入删除会影响多个后缀,末尾插入删除只影响一个后缀所以可以维护。

为什么字符串末尾添加一个字符只影响一个后缀,因为在我们的理解中,添加一个字符不就是在之前每一个后缀后面都加了一个字符吗,这样每个后缀大小都变了啊?

因为其实我们维护的是前缀(也就是网上说的后缀平衡树只支持前段加入一个字符)

比如我们插入ABBCD,一次插入的话,我们维护的是:A,BA,BBA,CBBA,DCBBA

维护这样的前缀,我们就可以保证新插入一个字符,只多了一个新的后缀,不影响之前所有的后缀的大小关系。

相应的,之前height维护最长相同前缀,现在求height需要从末尾往前求,因为末尾才是一个新串的开头


后缀大小比较方法

想要新插入一个后缀,我们必须要能够比较出新后缀和老后缀的大小。

方法一:

二分+hash求两个节点最长相同前缀,然后下一位就能比较出大小,求LCP复杂度logn,算上插入,复杂度O(logn*logn)

方法二:

新插入的后缀由一个新加入的字符作为开头,后面一坨都是平衡树中原有的,我们给平衡树中每个节点都搞个tag值,用于直接表示这个后缀的大小。

新后缀和其他节点比较时,只需比较两者的第一个字符,以及两者去掉首字符对应后缀的tag即可。

这个方法比较复杂度为O(1),算上插入,复杂度为O(logn)

方法二的具体解释:

相当于线段树一样,每个点有一个l,r,该点的tag = (l+r)/2,左儿子的l ,r分别等于当前节点的l,tag,右儿子的l,r分别等于当前节点的tag,r。

插入当前的后缀后,父节点就可以搞好当前节点的l,r和tag

之后不同平衡树由于要维持平衡各自有不同的操作(我只会treap,其他不了解)

比如说旋转,树的父子关系发生变化,这个时候可咋办呀?直接整棵子树暴力重新搞tag!

这个复杂度想想是挺玄学的,听说是均摊O(1)


直接扔一坨代码,稍微改改可以过这题(指多组输入)

删除相关函数真的不确定对了没有!

​

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define debug(x) printf("---- %s ----\n",#x)
#define INF 1.00

const int N = 1e5+5;
char op[N];

struct SAT
{
    int ch[N][2];
    int pri[N];
    double l[N],r[N],tag[N];//维护tag需要的
    int height[N];
    int pre[N],suc[N];//前驱后继,相当于后缀数组sa[]数组
    int fa[N];//储存当前节点的父节点
    int sz,root;
    ll ans;
    int s[N];//储存插入的串
    unsigned long long hash[2][N],power[2][N],mi[2]={97,311};//双哈希,就是两遍哈希

    void init(){//初始化
        sz = 1;
        ans = root = 0;
        power[0][0] = power[1][0] = 1;
        for (int i=1;i<=1000;i++) power[0][i] = power[0][i-1]*mi[0], power[1][i] = power[1][i-1]*mi[1];
    }

    int rand(){//优化rand()
        static int seed=703; ///static 起到全局变量的效果
        return seed=int(seed*48271LL%2147483647);///48271,2147483647这两个数字应该很关键,然后longlong转int也很关键
    }

    int newnode(double L,double R){//新建节点
        ch[sz][0] = ch[sz][1] = 0;
        pri[sz] = rand();
        l[sz] = L,r[sz] = R,tag[sz] = (L+R)/2;
        pre[sz] = suc[sz] = fa[sz] = height[sz] = 0;
        return sz++;
    }

    bool cmp_hash(int a,int aa,int b,int bb){//判断串1的a~aa位和串2的b~bb位是否相等
        if (hash[0][aa]-hash[0][a-1]*power[0][aa-a+1] != hash[0][bb]-hash[0][b-1]*power[0][bb-b+1]) return false;
        if (hash[1][aa]-hash[1][a-1]*power[1][aa-a+1] != hash[1][bb]-hash[1][b-1]*power[1][bb-b+1]) return false;
        return true;
    }

    int lcp(int x,int y){//二分求两者的LCP,用于维护height
        int l = 0,r = min(x,y);
        while (l<=r){
            int p = l+r;//x-p+1~x,y-p+1~y
            bool flag = true;
            if (cmp_hash(x-p+1,x,y-p+1,y)) l = p+1;
            else r = p-1;
        }
        //printf("lcp(%d,%d) = %d\n",x,y,r);
        return r;
    }

    void maintain_height(int op){//维护height数组,op=0表示添加一个字符后维护height,op=1表示删除一个字符维护height
        //printf("before:ans = %d\n",ans);
        int now = sz-1;
        int bef = pre[now];
        int aft = suc[now];
        if (op==0){//插入了sz-1节点
            ans -= aft - height[aft];
            height[now] = lcp(now,bef);
            height[aft] = lcp(aft,now);
            ans += now -height[now] + aft - height[aft];
        }
        else {
            ans -= aft-height[aft] + now-height[now];
            height[aft] = lcp(bef,aft);
            ans += aft-height[aft];
        }
        //printf("after:ans = %d\n",ans);
    }

    void rebuild(int rt,double L,double R){//暴力重构整棵子树的tag
        double mid = (L+R)/2;
        if (ch[rt][0]) rebuild(ch[rt][0],L,mid);
        l[rt] = L,r[rt] = R,tag[rt] = mid;
        if (ch[rt][1]) rebuild(ch[rt][1],mid,R);
    }

    void rotate(int& rt,int d){//旋转
        double L = l[rt],R = r[rt];
        fa[ch[rt][d^1]] = fa[rt];//旋转要改变父子关系
        fa[rt] = ch[rt][d^1];
        int k = ch[rt][d^1];//下面四行就是旋转的基本操作
        ch[rt][d^1] = ch[k][d];
        ch[k][d] = rt;
        rt = k;
        rebuild(rt,L,R);//每次旋转都要重构子树
    }

    void insert(int f,int& rt,int p,int dd){//dd=0表示作为f的左儿子,dd=1表示作为f的右儿子,p是新插入节点的下标
        if (!rt){
            if (!dd) {
                rt = newnode(l[f],tag[f]);
                suc[rt] = f;
                pre[rt] = pre[f];
                if (f) pre[f] = rt;
                if (pre[rt]) suc[pre[rt]] = rt;
            }
            else {
                rt = newnode(tag[f],r[f]);
                pre[rt] = f;
                suc[rt] = suc[f];
                if (f) suc[f] = rt;
                if (suc[rt]) pre[suc[rt]] = rt;
            }
            fa[rt] = f;
        }
        else {
            int d = (s[p]<s[rt] || s[p]==s[rt] && tag[p-1]<tag[rt-1])? 0:1;//比较两个后缀的大小
            insert(rt,ch[rt][d],p,d);
            if (pri[ch[rt][d]] > pri[rt]) rotate(rt,d^1);///***维护大根堆
        }
    }

    void add(char c){
        int now = sz;
        s[now] = c;
        hash[0][now] = hash[0][now-1]*mi[0] + s[now];
        hash[1][now] = hash[1][now-1]*mi[1] + s[now];
        if (now==1){//第一个节点特殊处理
            root = 1;
            newnode(0,INF);
            fa[1] = 0;
        }
        else{//因为insert函数那么写了,需要那么几个参数,这里只能这么写了,码量有点大
            int d = (s[now]<s[root] || s[now]==s[root] && tag[now-1]<tag[root-1])? 0:1;
            insert(root,ch[root][d],now,d);
            if (pri[ch[root][d]] > pri[root]) rotate(root,d^1);
        }
        maintain_height(0);
    }

    int merge(int x,int y){//合并:类似无旋treap的merge,用于删除时把要删除的节点的两个儿子合并
        if (!x || !y) return x+y;
        else {
            if (pri[x]>pri[y]){
                ch[x][1] = merge(ch[x][1],y);
                fa[ch[x][1]] = x;
                return x;
            }
            else {
                ch[y][0] = merge(x,ch[y][0]);
                fa[ch[y][0]] = y;
                return y;
            }
        }
    }

    void remove(){//删掉当前末节点
        maintain_height(1);
        suc[pre[sz-1]] = suc[sz-1];
        pre[suc[sz-1]] = pre[sz-1];
        double lll = l[sz-1],rrr = r[sz-1];
        if (root == sz-1){//如果删除的是根节点,需要重新赋值根节点
            root = merge(ch[sz-1][0],ch[sz-1][1]);
            rebuild(root,lll,rrr);
        }
        else {
            int f = fa[sz-1];
            int d = ch[f][0]==sz-1? 0:1;
            int rt = merge(ch[sz-1][0],ch[sz-1][1]);
            rebuild(root,lll,rrr);
            fa[rt] = f;
            ch[f][d] = rt;
        }
        sz--;
    }
/*
    void DFS(){dfs(root);puts("");}
    void dfs(int now){
        if (ch[now][0]) dfs(ch[now][0]);
        printf("now = %d:",now);
        for (int i=now;i>=1;i--) printf("%c",s[i]);
        puts("");
        //printf("pre[%d]=%d,suc[%d]=%d\n",now,pre[now],now,suc[now]);
        //printf("pri[%d]=%d,tag[%d]=%.6f\n",now,pri[now],now,tag[now]);
        if (ch[now][1]) dfs(ch[now][1]);
    }
*/
}sat;

#define R sat.root

int main()
{
    //freopen("C:/Users/DELL/Desktop/input.txt", "r" ,stdin);
    //freopen("C:/Users/DELL/Desktop/myoutput.txt", "w" ,stdout);
    //int T;
    //scanf("%d",&T);
    //while (T--){
        sat.init();
        scanf("%s",op);
        int cnt = strlen(op);
        for0(i,0,cnt){
            if (op[i]=='-'){
                sat.remove();
            }
            else {
                sat.add(op[i]);
            }
/*
            puts("After every option:");
            sat.DFS();
*/
            printf("%d\n",sat.ans);
        }
    //}
    return 0;
}

[点击并拖拽以移动]
​

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值