けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 370 C - Word Ladder (4Q, 灰色, 300 点)

操作回数を最小化せよ、という問題だけど、実際には「複数ある場合は辞書順最小のものを求めよ」という部分に本質がある問題!

問題概要

文字列  S, T は文字数が等しい。 S に対して「1 文字変更する」という操作を繰り返すことで  T にしたい。

そのうちの最小回数のものを求めよ(最小回数を  M とする)。

複数通りあるときは、 i = 1, 2, \dots, M 回目の操作後の文字列  S をこの順に concat してできる文字列が辞書順最小であるものを求めよ。

制約

  •  1 \le |S| = |T| \le 100

考えたこと

最小回数はとてつもなく簡単に求められることに注意しよう!!(意外と忘れがちなのだ)

それは、 S_{i} \neq T_{i} であるような  i の個数である。 S, Tハミング距離などとも呼ばれている値である。

さて、 S M 回で  T にする方法というのは、次のように特徴づけられる。


その時点における  S について、 T と文字が異なる添字  i を好きなように選んで  S_{i} T_{i} に置き換える


さて、concat してできる文字列が辞書順最小であるためには、実は単純に、次のような Greedy 解法を考えれば良い(これは操作後の  S の長さが毎回等しいことから言える)。

  • 1 回目の操作後の文字列  S のうち、辞書順最小のものを新たな  S とする
  • 2 回目の操作後の文字列  S のうち、辞書順最小のものを新たな  S とする
  • ...
  •  M 回目の操作後の文字列  S のうち、辞書順最小のものを新たな  S とする

この処理は、愚直に  O(N^{3}) の計算量で実装できる。

コード

#include <bits/stdc++.h>
using namespace std;

int main() {
    string S, T;
    cin >> S >> T;

    // 文字列 S を文字列 T に向かって一文字変更してできる文字列のうち、辞書順最小のもの
    auto solve = [&]() -> string {
        string res = "";
        for (int i = 0; i < S.size(); i++) {
            if (S[i] != T[i]) {
                char c = S[i];
                S[i] = T[i];
                if (res == "") res = S;
                else res = min(res, S);
                S[i] = c;  // 元に戻しておく
            }
        }
        return res;
    };

    vector<string> res;
    while (S != T) {
        S = solve();
        res.push_back(S);
    }
    cout << res.size() << endl;
    for (auto str : res) cout << str << endl;
}