zoukankan      html  css  js  c++  java
  • 【牛客2020多校训练营第一场A题】B-Suffix Array 结论 or 思路 + 后缀数组

    B-Suffix Array

    题意

    给出一个字符串 s ,它的以下标 i 为开头的后缀为 (s_i) ,给出 B 函数,对每个后缀进行 B 函数的运算。定义 B 函数如下:

    对于一个字符串 t ,根据以下运算得到其 b 数组:

    (b_i = min((i-j)_{t_j = t_i,j < i}))

    (b_i = 0)

    对 s 的每个后缀做完 B 函数运算之后,根据每个后缀的 b 数组,按照字典序进行升序排序,依次输出。

    题解1

    官方的题解是个结论,贴个图:

    可参考此博客2020牛客多校第一场 B题Suffix Array(结论+后缀数组)

    image-20200716162429282

    代码

    #include <bits/stdc++.h>
    #define pb push_back
    const int N = 1e5 + 10;
    const int inf = 0x3f3f3f3f;
    typedef long long ll;
    typedef unsigned long long ull;
    using namespace std;
     
    int sa[N], rk[N], oldrk[N], cnt[N], pos[N], valk[N],la[2];
    char str[N];
    int cmp(int x, int y, int k)
    {
        return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
    }
    void getsa(int *s,int n)
    {
        int m=n;
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++) ++cnt[rk[i]=s[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 *= 2)
        {
            int num = 0;
            for (int i = n - k + 1; i <= n; i++)
                pos[++num] = i;
            for (int i = 1; i <= n; i++)
            {
                if (sa[i] > k)
                    pos[++num] = sa[i] - k;
            }
            for(int i=1;i<=m;i++) cnt[i]=0;
            //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;
            for(int i=1;i<=n;i++) oldrk[i]=rk[i];
            // memcpy(oldrk, rk, sizeof(rk));
            for (int i = 1; i <= n; i++)
                rk[sa[i]] = cmp(sa[i], sa[i - 1], k) ? num : ++num;
            m = num;
            if (num == n)
                break;
        }
        for (int i = 1; i <= n; i++)
            rk[sa[i]] = i;
    }
    int main()
    {
        int n;
        while (~scanf("%d", &n))
        {
            scanf("%s",str+1);
            la[0]=la[1]=n+1;
            for(int i=n;i;i--)
            {
                if(la[str[i]-'a']==n+1) valk[i]=n;
                else valk[i]=la[str[i]-'a']-i;
                la[str[i]-'a']=i;
            }
            valk[n+1]=n+1;
            getsa(valk,n+1);
            for(int i=n;i;i--)
                printf("%d ",sa[i]);
            printf("
    ");
        }
        return 0;
    }
    /*
    */
    

    题解2

    看了一位博主的思路:2020年牛客多校A题_weixin_43965698的博客-CSDN博客

    其实如果 (b_i=min((j-i)_{t_j=t_i,j>i})) ,那么我们就可以直接使用后缀数组排序。

    但是这题写一个样例其实还是可以发现后缀数组的痕迹。

    套用博主的图:

    image-20200716162835773

    将每个后缀的 b 数组写出来之后还是可以发现,D部分其实存在前后缀的关系。

    A部分根据字符 a 和字符 b 出现的出现的位置即可得出,并且可以根据它的长度进行排序。

    首先根据A部分进行排序,如果A部分相同,那么我们再按照 D 部分排序,我们可以知道 D 部分是s 转化出的 b 数组的后缀,所以我们直接对 b 进行后缀数组排序,比较的时候直接比较排名即可。

    注意:根据 A 的长度排序其实存在一些错误,比如 baa 这个样例。

    所以对于只有 a 或者 b 的后缀,我们要在最后加上一个 a 或者 b。

    这样对于结果并不会造成影响,可以自己模拟一下。

    代码中有些注释:

    代码

    #include <bits/stdc++.h>
    #define emplace_back push_back
    #define pb push_back
    using namespace std;
    typedef long long ll;
    const int mod = 1e9 + 7;
    const int inf = 0x3f3f3f3f;
    const int N = 1e5 + 10;
    
    char str[N];
    int sa[N], rk[N], oldrk[N], pos[N], cnt[N];
    int cmp(int x, int y, int k)
    {
        return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
    }
    void getsa(int *s, int n)
    {
        int m = n;
        memset(cnt, 0, sizeof(cnt));
        for (int i = 1; i <= n; i++)
            ++cnt[rk[i] = s[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 *= 2)
        {
            int num = 0;
            for (int i = n - k + 1; i <= n; i++)
                pos[++num] = i;
            for (int i = 1; i <= n; i++)
            {
                if (sa[i] > k)
                    pos[++num] = sa[i] - k;
            }
            for(int i = 0; i <= m; i++)
                cnt[i] = 0;
            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;
            for(int i = 1; i <= n; i++)
                oldrk[i] = rk[i];
            for(int i = 1; i <= n; i++)
                rk[sa[i]] = cmp(sa[i], sa[i-1], k) ? num : ++num;
            if(num == n)
                break;
            m = num;
        }
        for (int i = 1; i <= n; i++)
            rk[sa[i]] = i;
    }
    int len[N], b[N], ans[N];//len[i]表示后缀i的A部分长度,b数组是后缀1的b数组
    int cmp2(int a, int b)
    {
        if(len[a] ==  len[b])//A部分相同,按照D部分的排名排序
            return rk[a + len[a]] < rk[b + len[b]];
        return len[a] < len[b];
    }
    int main()
    {
    //    freopen("D:\1.in","r",stdin);
    //    freopen("D:\my.out","w",stdout);
        int n;
        while (~scanf("%d%s", &n, str + 1))
        {
            int lst[2];
            lst[0] = lst[1] = n + 1;//先在最后加上a和b
            for (int i = n; i; i--)
            {
                if(str[i] == 'a')
                    len[i] = lst[1] - i + 1;
                else
                    len[i] = lst[0] - i + 1;
                lst[str[i] - 'a'] = i;
            }
            lst[0] = lst[1] = 0;
            for (int i = 1; i <= n; i++)
            {
                ans[i] = i;
                if (lst[str[i] - 'a'])
                    b[i] = i - lst[str[i] - 'a'] + 1;
                else
                    b[i] = 1;
                lst[str[i] - 'a'] = i;
            }
            getsa(b, n);
            rk[n + 1] = -1;//最后要加上两个,参考样例ab
            rk[n + 2] = -2;
            sort(ans + 1, ans + 1 + n, cmp2);
            for(int i = 1; i <= n; i++)
                printf("%d ",ans[i]);
            printf("
    ");
        }
        return 0;
    }
    /*
    
    babaa
    abbaa
    ababa
    bbaba
    baabb
    */
    
    
  • 相关阅读:
    windbg条件断点总结
    使用openssl命令剖析RSA私钥文件格式
    RSA读取密钥——使用openssl编程
    OPENSSL中RSA私钥文件(PEM格式)解析【一】
    电商系统架构——系统鸟瞰图
    构建高并发高可用的电商平台架构实践
    一些PHP性能的优化
    CentOS的Gearman安装
    php安装gearman扩展实现异步分步式任务
    使用 Gearman 实现分布式处理
  • 原文地址:https://www.cnblogs.com/valk3/p/13323693.html
Copyright © 2011-2022 走看看