とても面白かった。文字列に操作を 回施して、操作後の文字列の辞書順最小のものを求める問題。Suffix Array のよい練習問題でもある。
問題概要
英小文字のみからなる長さ の文字列
が与えられる。この文字列に対して、以下のいずれかの作業をちょうど
回実行する
- 文字列
の好きな箇所に好きな英小文字を挿入する
- 文字列
中の好きな文字を削除する
- 文字列
の好きな文字を好きな英小文字に変更する
できあがる文字列のうち、辞書順最小な文字列を答えよ。
制約
考えたこと
まずは様子を掴んでみる
= "abc",
→ "aabc"
= "acb",
→ "aab"
= "aba",
→ "aa"
- ...
やっていくうちに、'a' だけにしてしまった方がいいやつと、'a' を先頭に多くした方がいいやつがあるらしいことに気がついた。
'a' だけにできるやつ
文字列 中の 'a' 以外の文字の個数を
としたとき、
ならば 'a' だけにできる。'a' だけにできるならば、文字数は少ない方がいいので
文字にする。つまり、答えは
string(N-K, 'a')
になる。
なお、 なので空文字列にはできないことに注意する。
'a' だけにできないやつ
これはどうしようもないので、先頭に 'a' を付け加えて引き伸ばすことにする。しかし、すでにある文字を 'a' に変更した方が良い場合もある。そこで、次のように考えることにした。
- 先頭から
文字分については、'a' でない文字をすべて 'a' に変更した上で、あまった操作回数は 'a' を先頭に挿入していく
文字目以降は何もしない
この場合だけ考えればよいことは比較的すぐにわかった。'a' 以外の文字をそのままにした場合、それ以降の文字を削除したり変更したりする意味はないのだ。
あとは、 に対して、辞書順最小のものを求めればよい (ただし 'a' 以外の文字が
以内の範囲のみ)。
比較方法は次のように考えることにした。
'a' の個数が最大のもの、ただし 'a' の個数が等しい場合は、そのうちの S.substr(i) の辞書順が最小のもの
S.substr(i) の辞書順については Suffix Array を用いることで比較できる。
全体として、計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } // SA-IS (O(N)) template<class Str> struct SuffixArray { // data Str str; vector<int> sa; // sa[i] : the starting index of the i-th smallest suffix (i = 0, 1, ..., n) vector<int> lcp; // lcp[i]: the lcp of sa[i] and sa[i+1] (i = 0, 1, ..., n-1) int& operator [] (int i) { return sa[i]; } // constructor SuffixArray(const Str& str_) : str(str_) { build_sa(); } void init(const Str& str_) { str = str_; build_sa(); } void build_sa() { int N = (int)str.size(); vector<int> s; for (int i = 0; i < N; ++i) s.push_back(str[i] + 1); s.push_back(0); sa = sa_is(s); calcLCP(s); } // SA-IS // upper: # of characters vector<int> sa_is(vector<int> &s, int upper = 256) { int N = (int)s.size(); if (N == 0) return {}; else if (N == 1) return {0}; else if (N == 2) { if (s[0] < s[1]) return {0, 1}; else return {1, 0}; } vector<int> isa(N); vector<bool> ls(N, false); for (int i = N - 2; i >= 0; --i) { ls[i] = (s[i] == s[i + 1]) ? ls[i + 1] : (s[i] < s[i + 1]); } vector<int> sum_l(upper + 1, 0), sum_s(upper + 1, 0); for (int i = 0; i < N; ++i) { if (!ls[i]) ++sum_s[s[i]]; else ++sum_l[s[i] + 1]; } for (int i = 0; i <= upper; ++i) { sum_s[i] += sum_l[i]; if (i < upper) sum_l[i + 1] += sum_s[i]; } auto induce = [&](const vector<int> &lms) -> void { fill(isa.begin(), isa.end(), -1); vector<int> buf(upper + 1); copy(sum_s.begin(), sum_s.end(), buf.begin()); for (auto d: lms) { if (d == N) continue; isa[buf[s[d]]++] = d; } copy(sum_l.begin(), sum_l.end(), buf.begin()); isa[buf[s[N - 1]]++] = N - 1; for (int i = 0; i < N; ++i) { int v = isa[i]; if (v >= 1 && !ls[v - 1]) { isa[buf[s[v - 1]]++] = v - 1; } } copy(sum_l.begin(), sum_l.end(), buf.begin()); for (int i = N - 1; i >= 0; --i) { int v = isa[i]; if (v >= 1 && ls[v - 1]) { isa[--buf[s[v - 1] + 1]] = v - 1; } } }; vector<int> lms, lms_map(N + 1, -1); int M = 0; for (int i = 1; i < N; ++i) { if (!ls[i - 1] && ls[i]) { lms_map[i] = M++; } } lms.reserve(M); for (int i = 1; i < N; ++i) { if (!ls[i - 1] && ls[i]) { lms.push_back(i); } } induce(lms); if (M) { vector<int> lms2; lms2.reserve(isa.size()); for (auto v: isa) { if (lms_map[v] != -1) lms2.push_back(v); } int rec_upper = 0; vector<int> rec_s(M); rec_s[lms_map[lms2[0]]] = 0; for (int i = 1; i < M; ++i) { int l = lms2[i - 1], r = lms2[i]; int nl = (lms_map[l] + 1 < M) ? lms[lms_map[l] + 1] : N; int nr = (lms_map[r] + 1 < M) ? lms[lms_map[r] + 1] : N; bool same = true; if (nl - l != nr - r) same = false; else { while (l < nl) { if (s[l] != s[r]) break; ++l, ++r; } if (l == N || s[l] != s[r]) same = false; } if (!same) ++rec_upper; rec_s[lms_map[lms2[i]]] = rec_upper; } auto rec_sa = sa_is(rec_s, rec_upper); vector<int> sorted_lms(M); for (int i = 0; i < M; ++i) { sorted_lms[i] = lms[rec_sa[i]]; } induce(sorted_lms); } return isa; } // prepair lcp vector<int> rsa; void calcLCP(const vector<int> &s) { int N = (int)s.size(); rsa.assign(N, 0), lcp.assign(N, 0); for (int i = 0; i < N; ++i) rsa[sa[i]] = i; int h = 0; for (int i = 0; i < N - 1; ++i) { int pi = sa[rsa[i] - 1]; if (h > 0) --h; for (; pi + h < N && i + h < N; ++h) { if (s[pi + h] != s[i + h]) break; } lcp[rsa[i] - 1] = h; } } }; int main() { int K; string S; cin >> S >> K; int N = S.size(); // Suffix Array を構築して rsa を取得 SuffixArray<string> sa(S); vector<int> rsa = sa.rsa; // a だけにできるか int nota = 0; for (auto c : S) if (c != 'a') ++nota; if (nota <= K) { cout << string(N - K, 'a') << endl; return 0; } nota = 0; pair<int,int> mi = {N, N}; int resid = -1; for (int i = 0; i < N; ++i) { if (nota > K) break; int num = i + (K - nota); int ord = rsa[i]; if (chmin(mi, make_pair(-num, ord))) resid = i; if (S[i] != 'a') ++nota; } cout << string(-mi.first, 'a') + S.substr(resid) << endl; }