AtCoder Regular Contest 081 E - Don't Be a Subsequence

E - Don’t Be a Subsequence

問題

英小文字のみからなる文字列で、文字列 SS の部分列でないようなもののうち最短のものを求めよ。 複数ある場合は、その中で辞書順最小のものを求めよ。

制約

  • 1S21051 \leq |S| \leq 2 \cdot 10^5

考察

以降、文字列 SS に対する解を f(S)f(S) とする。 SS が空文字列でもよいことに注意(この場合、解は a となる)。

まずは f(S)|f(S)| を求める。これは f(S)f(S) の先頭の文字 cc で場合分けすると求められる。

  • SS 中に cc が現れない場合。 cc 自体が条件を満たすので長さは 11 となる。
  • 現れる場合。 SS 中で現れる cc のうち一番前のものを SiS_i とすると、最適解は c+f(S[i+1,S))c + f(S_{[i+1,|S|)}) となる。

ここで S[i,S)S_{[i,|S|)}SSii 文字目以降からなる文字列を表す。

よって全ての 0iS0 \leq i \leq |S| について以下を求めればよい。

  • 各文字 cc について、 S[i,S)S_{[i,|S|)} 中で現れる cc のうち一番手前のものの index。
  • f(S[i,S))|f(S_{[i,|S|)})|

これは ii について降順に求めていくことで、全体で Θ(σS)\Theta(\sigma |S|) で求められる (σ\sigma は文字種数)。

上のアルゴリズムを使うと、「先頭が cc であり、条件を満たすような文字列の長さの最小値」が求められる。これが最小となるような cc のうち辞書順最小のものを先頭にすれば、 f(S)f(S) の復元ができる。

実装例

cc が含まれない場合の処理を場合分けなしでやろうとしたら、却って実装がバグった。 自然に解釈できない番兵は使うべきではない。

提出 #18405139 - AtCoder Regular Contest 081

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <vector>
#include <string>

template <class T>
std::vector<T> vec(int len, T elem) { return std::vector<T>(len, elem); }

using namespace std;

void solve() {
    string s;
    cin >> s;
    int n = s.length();

    vector<int> len(n + 1, n);  // S[i, n)に対する解の長さ
    len[n] = 1;
    auto nxt = vec(n + 1, vec(26, -1));
    // S[i, n)で一番手前にあるcの位置(なければ-1)

    for (int i = n - 1; i >= 0; --i) {
        nxt[i] = nxt[i + 1];
        nxt[i][s[i] - 'a'] = i;

        for (auto j : nxt[i]) {
            int nlen = (j == -1 ? 0 : len[j + 1]) + 1;
            len[i] = min(len[i], nlen);
        }
    }

    int i = 0, l = len[0];
    while (l > 0) {
        int nc = -1;  // 次の文字

        for (int c = 0; c < 26; ++c) {
            auto j = nxt[i][c];
            int nlen = (j == -1 ? 0 : len[j + 1]) + 1;

            if (nlen == l) {
                nc = c;
                break;
            }
        }

        cout << char(nc + 'a');
        i = nxt[i][nc] + 1;
        --l;
    }
    cout << "\n";
}
Built with Hugo
テーマ StackJimmy によって設計されています。