mayoko’s diary

プロコンとかいろいろ。

Typical DP Contest G - 辞書順

解法

まず少し小さい問題として, 「答えが Eel になるかどうか」というのを考えてみます。これは, 「部分文字列は何種類考えられるか」という問題が解ければ良いです。

文字列の各位置を頂点とみなします。
各頂点から, 'a', 'b', ..., 'z' に到達する, 最も index の近い頂点をメモしておきます(出来たグラフを G とする)。
G を求めておけば, dp[i] = (i 番目の頂点から始まる部分文字列の数) が求まります。i = 0 を root のような感じにすると書きやすいです。これで dp[0] < K なら答えは Eel です。

そうでない場合は, 1 文字目から順に文字を決めていきます。'a' から始まる文字列が a( < K ) 個あって, 'b' から始まる文字列が b 個あった時, a+b >= K なら, 1 文字目は 'b' に決まって, さらに 2 文字目は…と続けていきます。

typedef unsigned long long ull;
const int MAXN = 1000100;
vector<int> G[MAXN];
ull dp[MAXN];

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    string s;
    ull K;
    cin >> s;
    cin >> K;
    vector<int> edge(26, -1);
    int N = s.size();
    for (int i = N; i > 0; i--) {
        G[i] = edge;
        edge[s[i-1]-'a'] = i;
    }
    G[0] = edge;
    for (int i = N; i >= 0; i--) {
        dp[i] = (i>0);
        for (int j = 0; j < 26; j++) {
            int to = G[i][j];
            if (to != -1) {
                dp[i] += dp[to];
                if (dp[i] > K) dp[i] = K+1;
            }
        }
    }
    if (dp[0] < K) cout << "Eel" << endl;
    else {
        string ans;
        int now = 0;
        ull k = 0;
        while (k < K) {
            for (int j = 0; j < 26; j++) {
                int to = G[now][j];
                if (to == -1) continue;
                if (k+dp[to] < K) {
                    k += dp[to];
                } else {
                    k++;
                    now = to;
                    ans += (char)('a'+j);
                    break;
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}