zoukankan      html  css  js  c++  java
  • 后缀数组入门

    转载自OI-wiki

    后缀数组相关定义

    sa[i]:表示按照字典序排序后,第i名后缀开头下标

    rk[i]:表示后缀i的排名

    后缀i:以下标i为开头的后缀

    暴力求法

    一切都从暴力开始,哈哈哈哈

    我们把n个后缀sort一遍,复杂度是O(n2logn)。

    倍增优化

    使用倍增的思想进行优化。

    1. 按照每个后缀的前1个字母排序。

    2. 按照每个后缀的前2个字母排序

    3. 按照每个后缀的前4个字母排序

      ...

    4. 直到每个后缀的排名不一样

    最多会排序logn次

    具体排序

    sa2

    解释:

    首先第一次按照每个后缀的第一个字母排序,每个后缀的排名为rk[i]

    第二次按照后缀的前两个字母排序,将第i个后缀的排名和第i+1个后缀的排名作为第i个后缀的第一二关键字,如果i+1>n,第二关键字为0,进行排序。

    这时rk[i]表示的是按照前两个字母排序后,后缀i的排名。

    那么我们如何按照后缀的前四个值排序呢?

    把第i个排名和第i+2的排名作为第i个的一二关键字,然后进行排序。

    ...

    如果这个过程中使用的是sort排序,那么复杂度是O(nlog2n),

    基数排序优化

    使用基数排序将一次排序的复杂度降为O(n)

    基数排序

    代码

    #include<bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef long long ll;
    const int N=1e6+10;
    const int mod=1e9+7;
    const int inf=0x3f3f3f3f;
    
    int pos[N],sa[N],cnt[N],rk[N],oldrk[N];
    char str[N];
    bool cmp(int x,int y,int k)
    {
        return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
    }
    void solve()
    {
        int m=200;
        int n=strlen(str+1);
        for(int i=1; i<=n; ++i)
            ++cnt[rk[i]=str[i]];
        for(int i=1; i<=m; ++i)
            cnt[i]+=cnt[i-1];
        for(int i=n; i; --i)
            sa[cnt[rk[i]]--]=i;
        for(int k=1; k<=n; k<<=1)
        {
            int num=0;
            /*
            pos[i]表示第二关键字排名为i的第一关键字的位置
            n-k+1~n都没有第二关键字,补0,所以它们的排名在前面
            */
            for(int i=n-k+1; i<=n; ++i)
                pos[++num]=i;
            /*
            遍历sa,如果sa[i]>k,说明排名为i的可以作为第二关键字
            */
            for(int i=1; i<=n; ++i)
            {
                if(sa[i]>k)
                    pos[++num]=sa[i]-k;
            }
            memset(cnt,0,sizeof(cnt));
            for(int i=1; i<=n; ++i)
                ++cnt[rk[i]];
            for(int i=1; i<=m; ++i)
                cnt[i]+=cnt[i-1];
            /*
            第一关键字相同的时候按照第二关键字由大到小进行排序
            */
            for(int i=n; i; --i)
                sa[cnt[rk[pos[i]]]--]=pos[i];
            /*
            排序完成之后,现在更新每个后缀的新排名
            */
            num=0;
            memcpy(oldrk,rk,sizeof(rk));//复制当前排名到oldrk。
            /*
            判断当前排名和前一排名的第一二关键字是否一样,如果一样num不用++
            否则num++
            使用cmp减少不连续内存访问,在数据范围较大时效果比较明显。
            */
            for(int i=1; i<=n; ++i)
                rk[sa[i]]=cmp(sa[i],sa[i-1],k)?num:++num;
            if(num==n)//有n个排名,排序完成
                break;
            m=num;//排名的最大值为num
        }
        for(int i=1; i<=n; ++i)
            rk[sa[i]]=i;
    }
    int main()
    {
        scanf("%s",str+1);
        solve();
        int len=strlen(str+1);
        for(int i=1;i<=len;i++)
            printf("%d ",sa[i]);
        printf("
    ");
        return 0;
    }
    

    height数组

    lcp(i,j):表示后缀i和后缀j的最长公共前缀长度

    height[i]=lcp(sa[i],sa[i-1])//第i名和第i-1名的最长公共前缀长度

    height[1]=0

    求height数组引理:

    height[rk[i]]>=height[rk[i-1]]-1

    证明:

    Snipaste_2020-05-11_10-27-34

    引自OI-WiKi。

    O(n)求height数组代码:

    利用引理暴力:

    for (i = 1, k = 0; i <= n; ++i) 
    {
      if (k) --k;//>=height[rk[i-1]]-1,所以减1
      while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
      ht[rk[i]] = k; 
    }
    

    height数组简单应用

    (lcp(sa[i],sa[j])=min(height[i+1]...height[j]))
    (lcp(i,j)=min(height[rk[i]+1]...height[rk[j]]))

    小声哔哔

    当多组输入的时候,最好让str[n+1]等于一个没有出现过的字符,虽然没有出过错。

    详情可见

  • 相关阅读:
    教你怎么做游戏运营数据分析
    经验|数据分析告诉我们的四个经验教训
    hdu 2074 叠筐 好有意思的绘图题
    asp 之 让实体中字段类型为DateTime的字段仅仅显示日期不显示时间
    将字符串中不同字符的个数打印出来
    Cocos2d-x 3.0final 终结者系列教程08-画图节点Node中的锚点和坐标系
    mysql数据库sql优化——子查询优化
    jQuery ajax 动态append创建表格出现不兼容ie8
    JavaScript关于闭包
    PatternSyntaxException:Syntax error in regexp pattern
  • 原文地址:https://www.cnblogs.com/valk3/p/12912485.html
Copyright © 2011-2022 走看看