LOJ #3103. 「JSOI2019」節日慶典

題意

給定字符串 \(S\) ,對於 \(S\) 的每個前綴 \(T\)\(T\) 所有循環同構串的字典序最小的串,輸出其起始下標。(如有多個輸出最靠前的)

\(|S| \le 3 \times 10^6\)

題解

本文參考了官方題解。

假設我們現在考慮前綴 \(S[1 \dots k]\) ,我們考慮哪些起始位置可能成爲答案,我們稱作候選點。也就是對於這些候選點來說,對於 \(i \ge k\) ,他們永遠都會比非候選點更加優秀。

我們首先可以通過不循環移位比出他們的字典序的話,肯定可以直接看出哪個一定不是候選點。

性質一:假設兩個位置 \(i < j\) 。 如果 \(\mathrm{lcp}(S[i \dots n],S[j \dots n]) \le k − j\) , 那麼 \(i,j\) 之間肯定有一個不是候選點.

讀者自證不難,利用上這個性質纔是關鍵。

我們假設得到 \(S[1 \dots k - 1]\) 的候選點集 \(P\) ,對於 \(i, j \in P, i < j\) 那麼一定有 \(\mathrm{lcp}(S[i \dots n], S[j \dots n]) > (k - 1) - j\) ,我們只需要找出是否存在一個 \(\mathrm{lcp}(S[i \dots n],S[j \dots n]) \le k − j\) 即可排除一個候選點。

我們顯然只需要比較 \(S[i + k - j]\)\(S[k]\) 就能比出來了,但是枚舉所有點對是十分浪費的一件事。我們只考慮維護一個棧,每次比較棧頂兩個元素,留下較優的元素即可。

然後這樣看起來還是 \(\mathcal O(n^2)\) 的,但似乎能跑前 \(50pts\) 。(也許有更嚴謹的更優複雜度吧)

然後還需要利用一個神奇的性質優化候選點數。

性質二:對於兩個點 \(i < j\) , 假設 \(\mathrm{lcp}(S[i \dots n],S[j \dots n]]) > k − j\) , 如果有 \(k − j \ge j − i\) , 那麼 \(j\) 不是候選點。

這個性質看上去沒有那麼顯然了。

證明:這個性質是有最小循環表示的某個性質得來的, 假設串 \(S = S_1 S_1 S_2\) ,其中 \(S_1, S_2\) 是任意兩個子串。

  • 要麼有 \(S_1S_1S_2 \le S_1S_2 S_1 \le S_2S_1S_1\)
  • 要麼有 \(S_1S_1S_2 \ge S_1S_2 S_1 \ge S_2S_1S_1\)

這個討論 \(S_1, S_2\) 字典序大小不難發現。

那麼如果有 \(k - j \ge j - i\) 那麼 \(S[1 \dots k]\) 形如 \(ABBC\) ,那麼我們把這兩個後綴即可用 \(BBCA\)\(BCAB\) 表示。把 \(S_1\) 設成 \(B\)\(S_2\) 設成 \(CA\) ,那麼其實就是 \(S_1S_1S_2\)\(S_1S_2S_1\) ,顯然後者一定會被另外兩個循環串包在中間,一定不如其他兩個中的一個優。

利用上了這個性質,那麼就有相鄰兩個候選點距離翻倍,那麼只有 \(\mathcal O(\log n)\) 個候選點了。

這樣的話,看似我們可以利用各種後綴數據結構在 \(\mathcal O(n \log n)\) 內輕鬆愉悅的解決。

實則不然。。。除非你用 \(\text{SA-IS}\) ,那當我沒說。

我們預處理那裏的複雜度要儘量降低,我們還需要知道一個性質。

性質三:對於任意兩個候選點 \(i < j\) 那麼 \(S[j \dots k]\)\(S[i \dots k]\) 的一個前綴。

這個利用性質一不難發現。

那麼我們發現我們每次其實只需要比較一個後綴和原串的字典序大小,這正好契合了 \(\mathrm{ExKmp}\) 的用途。

不會的話可以看我之前的學習筆記 qwq

然後預處理就變成 \(\mathcal O(n)\) ,總複雜度是 \(\mathcal O(n \log n)\)

總結

求區間最小(循環)後綴,都可以考慮候選點只有 \(\mathcal O(\log n)\) 個的神奇性質。

代碼

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("3103.in", "r", stdin);
    freopen ("3103.out", "w", stdout);
#endif
}

const int N = 3e6 + 1e3;

void Get_Next(char *S, int *next) {
    int lenS = strlen(S + 1), p = 1, pos;
    next[1] = lenS;
    while (p + 1 <= lenS && S[p] == S[p + 1]) ++ p;
    next[pos = 2] = p - 1;

    For (i, 3, lenS) {
        int len = next[i - pos + 1];
        if (len + i < p + 1) next[i] = len;
        else {
            int j = max(p - i + 1, 0);
            while (i + j <= lenS && S[j + 1] == S[i + j]) ++ j;
            p = i + (next[pos = i] = j) - 1;
        }
    }
}

char S[N]; int lcp[N], n; vector<int> cur;

inline int cmp(int p, int len) {
    return lcp[p] >= len ? 0 : (S[lcp[p] + 1] < S[p + lcp[p]] ? 1 : -1);
}

inline int cmp(int x, int y, int len) {
    static int res; assert(x > y);
    if ((res = cmp(y + (len - x + 1), x - y))) return res > 0 ? x : y;
    if ((res = cmp(x - y + 1, y - 1))) return res > 0 ? y : x;
    return y;
}

int main () {

    File();

    scanf ("%s", S + 1); n = strlen(S + 1); Get_Next(S, lcp);

    For (k, 1, n) {
        vector<int> tmp(1, k);
        for (int i : cur) {
            while (!tmp.empty() && S[i + k - tmp.back()] < S[k]) tmp.pop_back();
            if (tmp.empty() || S[i + k - tmp.back()] == S[k]) {
                while (!tmp.empty() && 
                        k - tmp.back() >= tmp.back() - i) tmp.pop_back();
                tmp.push_back(i);
            }
        }

        cur = tmp; int ans = cur[0];
        For (i, 1, cur.size() - 1) ans = cmp(ans, cur[i], k);
        printf ("%d%c", ans, k == kend ? '\n' : ' ');
    }

    return 0;

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章