B-Suffix Array (sort排序)牛客多校第一场A题

B-Suffix Array

题意:给你一个字符串,同时定义一个 B B B函数,对于一个字符串他的B函数为: B ( s 1 s 2 s 3 . . . s n ) = t 1 t 2 t 3 . . . t n B(s_1s_2s_3...s_n) = t_1t_2t_3...t_n B(s1s2s3...sn)=t1t2t3...tn其中 t i = m i n j < i , s [ j ] = s [ i ] , ( i − j ) t_i = min_{jti=minj<i,s[j]=s[i],(ij)如果前面没有出现 s [ i ] s[i] s[i]则对应的 t [ i ] = 0 t[i]=0 t[i]=0
然后对每个后缀的B函数进行排序。
做法:我们先不说结论,我们就说说,正常的思路该怎么做,或者说看起来想得出来的做法。
下面的做法只适用于字符集为2的情况下。
首先我们目标是对后缀进行排序,如果能够找到比较任意两个的后缀的比较方法那么就可以用 n l o g n nlogn nlogn次比较排好序,如何判断分为以下几步。

  • 我们可以发现对于两个后缀开头的第一个一定是 0 0 0,然后如果出现的是一样的就是一串 1 1 1,比如 a a a a a = > 01111 aaaaa=>01111 aaaaa=>01111,直到出现第一个不相同的字母出现的一个 0 0 0,比如 a a a a a b = > 011110 aaaaab=>011110 aaaaab=>011110,因此我们对于任意两个后缀可以通过比较他们的开头的相同的字符的长度,我们把 i i i位置处的后缀的开头的相同的字符长度记为 d i s [ i ] dis[i] dis[i],较短者就证明他最先出现一个 0 0 0肯定他的字典序比较小(这个与字符并没有关系)。比如: a a a b b = > 01101 , b b a a a = > 01011 aaabb=>01101,bbaaa=>01011 aaabb=>01101,bbaaa=>01011.
  • 然后我们讨论长度相等的情况。我们可以发现如果长度发现 i i i i + d i s [ i ] i+dis[i] i+dis[i]这个位置的字符串一定是包含了 a a a b b b的(如果不包含那么 d i s [ i ] dis[i] dis[i]会更大),这个是有用的,根据 B B B函数的定义我们可以发现 如果对于一个字符串不断的加入字符,其函数值与 a a a b b b出现的有关,如果 a a a b b b都出现了那么,前面在怎么加入字符对后面的函数都没有影响,即 a a a b b b都出现了可以保护后面的子串的 B B B函数,比如 a b a a a b = > 102114 abaaab=>102114 abaaab=>102114 a a a b b b在前两个都出现了,因此你在前面在怎么加入字符串,都不会改变2后面子串的函数值。
  • 根据上面所说的我们如果对 i i i j j j后缀比较大小, i + d i s [ i ] i+dis[i] i+dis[i]后面的 j + d i s [ j ] j+dis[j] j+dis[j]去除相同的部分 比如 a a b a a a = > 010 ( 21 ) 1 aabaaa=>010(21)1 aabaaa=>010(21)1 b b a b b a = > 010 ( 21 ) 3 bbabba=>010(21)3 bbabba=>010(21)3需要比较括号后的大小,有点像 l c p lcp lcp,实际上我们可以发现因为前面都出现了 a a a b b b因此他们在整个字符串的 B B B函数值都是与他们的后缀相同的。比如 a a b a a a b b a b b a = > 010 ( 21 ) 1413 ( 21 ) 3 aabaaabbabba=>010(21)1413(21)3 aabaaabbabba=>010(21)1413(21)3,这部分我们可以用后缀数组解决。然后比较整个字符串的 B B B函数值,即 B [ i + l c p + d i s [ i ] ] < ? B [ j + l c p + d i s [ j ] ] B[i+lcp+dis[i]]B[i+lcp+dis[i]]<?B[j+lcp+dis[j]]
  • 但不过上面还有一些细节还需要注意,比如 i + d i s [ i ] > n i+dis[i]>n i+dis[i]>n这种情况也是直接排前面的,比如 b a ba ba a a a
    我觉得这个做法,我们比赛还是有可能想出来的。。。
    具体看代码的 c m p cmp cmp函数,如果你的代码出现段错误,有可能就是 c m p cmp cmp写错了。总时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
#include "bits/stdc++.h"

using namespace std;
inline int read() {
    int x = 0;
    bool f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
    if (f) return x;
    return 0 - x;
}
#define ll long long
#define SZ(x) ((int)((x).size()))
#define all(x) (x).begin(),(x).end()

const int maxn = 2e5 + 10;
const ll mod = 998244353;

struct SA {
    int sa[maxn], ra[maxn], height[maxn];
    int t1[maxn], t2[maxn], c[maxn];
    void build(int *str, int n, int m) {
        str[n] = 0;
        n++;
        int i, j, p, *x = t1, *y = t2;
        for (i = 0; i < m; i++) c[i] = 0;
        for (i = 0; i < n; i++) c[x[i] = str[i]]++;
        for (i = 1; i < m; i++) c[i] += c[i - 1];
        for (i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
        for (j = 1; j <= n; j <<= 1) {
            p = 0;
            for (i = n - j; i < n; i++) y[p++] = i;
            for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;
            for (i = 0; i < m; i++) c[i] = 0;
            for (i = 0; i < n; i++) c[x[y[i]]]++;
            for (i = 1; i < m; i++) c[i] += c[i - 1];
            for (i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
            swap(x, y);
            p = 1;
            x[sa[0]] = 0;
            for (i = 1; i < n; i++)
                x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + j] == y[sa[i] + j]) ? p - 1 : p++;
            if (p >= n) break;
            m = p;
        }
        n--;
        for (int i = 0; i <= n; i++) ra[sa[i]] = i;
        for (int i = 0, j = 0, k = 0; i <= n; i++) {
            if (k) k--;
            j = sa[ra[i] - 1];
            while (str[i + k] == str[j + k]) k++;
            height[ra[i]] = k;
        }
        st_init(height, n);
    }
    int lg[maxn], table[23][maxn];
    void st_init(int *arr, int n) {
        if (!lg[0]) {
            lg[0] = -1;
            for (int i = 1; i < maxn; i++)
                lg[i] = lg[i / 2] + 1;
        }
        for (int i = 1; i <= n; ++i)
            table[0][i] = arr[i];
        for (int i = 1; i <= lg[n]; ++i)
            for (int j = 1; j <= n; ++j)
                if (j + (1 << i) - 1 <= n)
                    table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);
    }
    int lcp(int l, int r) {
        l = ra[l], r = ra[r];
        if (l > r) swap(l, r);
        ++l;
        int t = lg[r - l + 1];
        return min(table[t][l], table[t][r - (1 << t) + 1]);
    }
} sa;

int n, fw[maxn], a[maxn], dis[maxn], b[maxn];
char str[maxn];

bool cmp(int i, int j) {
    if (dis[i] != dis[j]) return dis[i] < dis[j];

    if (i + dis[i] > n) return true;
    if (j + dis[j] > n) return false;
    int lcp = sa.lcp(i + dis[i], j + dis[j]);
    return a[i + lcp + dis[i]] < a[j + lcp + dis[j]];
}

int main() {
    while (cin >> n >> str) {
        int pa = -1, pb = -1;
        for (int i = 0; i < n; i++) {
            if (str[i] == 'a') {
                if (pa == -1) a[i] = 1;
                else a[i] = i - pa + 1;
                pa = i;
            } else {
                if (pb == -1) a[i] = 1;
                else a[i] = i - pb + 1;
                pb = i;
            }
        }
        pa = pb = -1;
        for (int i = n - 1; i >= 0; i--) {
            if (str[i] == 'a') {
                if (pb == -1) dis[i + 1] = n - i;
                else dis[i + 1] = pb - i;
                pa = i;
            } else {
                if (pa == -1) dis[i + 1] = n - i;
                else dis[i + 1] = pa - i;
                pb = i;
            }
        }
        for (int i = 1; i <= n; i++) b[i] = i;
        sa.build(a, n, n + 2);
        sort(b + 1, b + n + 1, cmp);
        for (int i = 1; i <= n; i++) printf("%d ", b[i]);
        cout << endl;
    }
    return 0;
}

然后

说一说结论,只适用于两个字符集,对于字符串的后缀的 p v pv pv函数排序,等价于对 f w fw fw数组后缀排序,其中 f w [ i ] = m i n j > i , s [ j ] = s [ i ] ( j − i ) fw[i]=min_{j>i,s[j]=s[i]}(j-i) fw[i]=minj>i,s[j]=s[i](ji),如果对应的后面没有出现 s [ i ] s[i] s[i]则对应为 ∞ \infty 比如 a b a a b = > 231 ∞ ∞ abaab=>231\infty\infty abaab=>231,这个的后缀排序是 3 , 1 , 2 , 4 , 5 3,1,2,4,5 3,1,2,4,5倒过来就是样例的答案。(注意最后要加入一个很大很大的值,类似于后缀数组最后加入的 " # " "\#" "#")。这个证明我肯定是不晓得,我觉得非大佬比赛时肯定不知道这个结论吧。。。。
有兴趣的同学可以搜索这篇论文 P a r a m e t e r i z e d   S u f f i x   A r r a y s   f o r   B i n a r y   S t r i n g s Parameterized \ Suffix \ Arrays \ for \ Binary \ Strings Parameterized Suffix Arrays for Binary Strings
比赛时i题网络流弄了一个奇数环自闭。。。。。。

你可能感兴趣的:(字符串)