【牛客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
*/

原文地址:https://www.cnblogs.com/valk3/p/13323693.html