BOJ 1099 - 알 수 없는 문장 링크
(2024.03.20 기준 G3)
하나의 문장과 개의 단어가 주어진다. 문장은 단어를 공백없이 붙여쓴 것이며, 붙여쓸 때 단어는 각 문자의 순서를 바꿀 수 있다. 단, 원래 단어의 문자마다 위치가 다른 문자의 개수만큼 비용이 든다.
문장을 주어지는 개의 단어로 해석할 때, 비용의 최솟값 출력
문자열 DP
는 문자열 와 에서 동일한 위치인데 다른 문자인 개수.
는 문자열 의 번째 인덱스부터 번째 인덱스 직전까지의 부분 문자열.
는 문자열 와 의 이루어진 소문자의 종류와 개수가 같은지 판별.모든 인덱스 마다 증가하는 순서대로 살펴볼 때, 를 만족하면 다음과 같은 점화식을 만족한다.
#include <bits/stdc++.h>
using namespace std;
const int MAX = 51;
int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    string S; cin >> S;
    int N; cin >> N;
    vector<string> W(N); for (int i = 0; i < N; i++) cin >> W[i];
    /*
    f(a, b) : 동일한 위치인데 다른 문자인 개수 (|a| = |b|)
    g(s, i, j) : s의 i번째 인덱스부터 j번째 인덱스 직전까지의 부분 문자열
    h(a, b) : a와 b의 이루어진 소문자의 종류와 개수가 같은지 판별
    h(g(S, i-|word|, i), word)를 만족하면
    dp(i) = min(dp(i), dp(i-|word|) + f(g(S, i-|word|, i), word))
    */
    int dp[MAX], ct[26], res;
    bool valid;
    dp[0] = 0; fill(dp + 1, dp + MAX, -1);
    for (int i = 1, sz = S.size(); i <= sz; i++)
        for (string word: W)
            if (word.size() <= i && ~dp[i - word.size()]){ // 이을 수 있어야 한다.
                // h(g(S, i-|word|, i), word)를 만족하는지 확인
                fill(ct, ct + 26, 0);
                for (int j = 0, sz_ = word.size(); j < sz_; j++){
                    ++ct[S[i - sz_ + j] - 97];
                    --ct[word[j] - 97];
                }
                valid = true;
                for (int j = 0; j < 26; j++) if (ct[j]){
                    valid = false;
                    break;
                }
                if (!valid) continue;
                // f(g(S, i-|word|, i), word)를 구한다.
                res = 0;
                for (int j = 0, sz_ = word.size(); j < sz_; j++) if (S[i - sz_ + j] != word[j]) ++res;
                // dp(i) = min(dp(i), dp(i-|word|) + f(g(S, i-|word|, i), word))
                if (~dp[i]) dp[i] = min(dp[i], dp[i - word.size()] + res);
                // 현재 dp 값이 초기값일 경우
                else dp[i] = dp[i - word.size()] + res;
            }
    cout << dp[S.size()];
}
import sys; input = sys.stdin.readline
S = input().strip()
N = int(input())
W = [input().strip() for _ in range(N)]
'''
f(a, b) : 동일한 위치인데 다른 문자인 개수 (|a| = |b|)
g(s, i, j) : s의 i번째 인덱스부터 j번째 인덱스 직전까지의 부분 문자열
h(a, b) : a와 b의 이루어진 소문자의 종류와 개수가 같은지 판별
h(g(S, i-|word|, i), word)를 만족하면
dp(i) = min(dp(i), dp(i-|word|) + f(g(S, i-|word|, i), word))
'''
dp = [-1] * (len(S) + 1)
dp[0] = 0
for i in range(1, len(S) + 1):
    for word in W:
        if len(word) <= i and ~dp[i - len(word)]: # 이을 수 있어야 한다.
            # h(g(S, i-|word|, i), word)를 만족하는지 확인
            ct = [0] * 26
            for j in range(len(word)):
                ct[ord(S[i - len(word) + j]) - 97] += 1
                ct[ord(word[j]) - 97] -= 1
            valid = True
            for j in range(26):
                if ct[j]:
                    valid = False
                    break
            if not valid:
                continue
            # f(g(S, i-|word|, i), word)를 구한다.
            res = 0
            for j in range(len(word)):
                if S[i - len(word) + j] != word[j]:
                    res += 1
            # dp(i) = min(dp(i), dp(i-|word|) + f(g(S, i-|word|, i), word))
            if ~dp[i]:
                dp[i] = min(dp[i], dp[i - len(word)] + res)
            else: # 현재 dp 값이 초기값일 경우
                dp[i] = dp[i - len(word)] + res
print(dp[-1])