F - Anti-DDoS Editorial by evima

別解

入力文字列を反転して、Anti-SoDD 文字列を数えることもできます。この場合、以下の条件のいずれかを満たす文字列を数えることになります。

  1. 大文字を含まない。
  2. 大文字を含み、最初の大文字以降に小文字を含まない。
  3. 大文字を含み、最初の大文字以降に小文字を含み、「最初の大文字以降の最初の小文字」以降に同じ大文字が複数回現れない。

条件 3 を満たす文字列は、「最初の大文字以降に現れる最初の小文字」で分割して、それ以降に現れる大文字の個数で分類すれば数えられます。この過程で他の条件も処理できます。

実装例(C++)

#include <algorithm>
#include <iostream>
#include <unordered_set>
#include <atcoder/modint>
using namespace std;
using mint = atcoder::modint998244353;

vector<mint> fact, ifact, p26;
void init(int N){
    fact.resize(N + 1, 1);
    ifact.resize(N + 1);
    p26.resize(N + 1, 1);
    for(int i = 0; i < N; ++i){
        fact[i + 1] = fact[i] * (i + 1);
        p26[i + 1] = p26[i] * 26;
    }
    ifact[N] = fact[N].inv();
    for(int i = N; i > 0; --i){
        ifact[i - 1] = ifact[i] * i;
    }
}

mint binom(int n, int r){
    return 0 <= r && r <= n ? fact[n] * ifact[r] * ifact[n - r] : 0;
}

int upper(char c){
    return c == '?' ? 26 : isupper(c) ? 1 : 0;
}

int lower(char c){
    return c == '?' ? 26 : islower(c) ? 1 : 0;
}

int main(){
    string S;
    cin >> S;
    reverse(S.begin(), S.end());
    int N = S.size();
    init(max(26, N));
    vector<vector<mint>> dp(N + 1, vector<mint>(2));
    dp[0][0] = 1;
    for(int i = 0; i < N; ++i){
        dp[i + 1][0] += dp[i][0] * lower(S[i]);
        dp[i + 1][1] += dp[i][0] * upper(S[i]);
        dp[i + 1][1] += dp[i][1] * upper(S[i]);
    }
    mint ans = dp[N][0] + dp[N][1];
    int q = 0;
    unordered_set<char> s;
    for(int i = N - 1; i >= 0; --i){
        mint sub = 0;
        int u = s.size();
        for(int v = 0; v <= min(26 - u, q); ++v){
            sub += binom(q, v) * fact[26 - u] * ifact[26 - u - v] * p26[q - v];
        }
        ans += sub * lower(S[i]) * dp[i][1];
        if(S[i] == '?'){
            ++q;
        }else if(isupper(S[i])){
            if(s.count(S[i])){
                break;
            }
            s.insert(S[i]);
        }
    }
    cout << ans.val() << endl;
}

posted:
last update: