#include #include #include #include using namespace std; using namespace atcoder; using mint = modint998244353; int main() { string s; cin >> s; int n = s.size(); // 各文字列目の時にどの数字を与えるとどのインデックスに移動するか vector mov(n + 1, vector(10)); // 正解の次の文字を選んだ時 for (int i = 0; i < n; i++) mov[i][s[i] - '0'] = i + 1; // 後ろに戻ってしまう先の座標を予め保持しておく。これが√N+logNであることを期待している vector jump(n + 1); jump[0] = true; // 間違った数字の選んだ先はz_algorithmを使えば一番長く復帰できる場所がわかる auto z = z_algorithm(s); for (int i = 1; i < n; i++) { int to = i + z[i]; if (mov[to][s[z[i]] - '0'] == 0) { mov[to][s[z[i]] - '0'] = z[i] + 1; jump[z[i] + 1] = true; } } // 戻り先を座標圧縮(0は定数項) vector zat; for (int i = 0; i < n; i++) { if (jump[i]) zat.push_back(i); } vector rev(n, -1); for (int i = 0; i < zat.size(); i++) { rev[zat[i]] = i + 1; } // 0を定数項として、戻り先の計数を含めた多項式として表す vector dp(s.size() + 1, vector(zat.size() + 1)); mint r10 = mint(10).inv(); for(int i=n-1;i>=0;i--){ dp[i][0] += 1; for (int v = 0; v < 10; v++) { int to = mov[i][v]; if (to > i) { for (int j = 0; j <= zat.size(); j++) { dp[i][j] += dp[i + 1][j] * r10; } } else { dp[i][rev[to]] += r10; } } // 戻り先に辿り着いた場合、ここで方程式を解いて一文字減らす // 998244353の場合、ここで分母に0が現れない保証はない(そういうことがないケースにしておくという制約はあり) if (rev[i] != -1) { int zi = rev[i]; assert(dp[i][zi] != 1); mint div = mint(1 - dp[i][zi]).inv(); dp[i][zi] = 0; for (int j = 0; j <= zat.size(); j++) { dp[i][j] *= div; } } } dp[0][0] -= n - 1; cout << dp[0][0].val(); return 0; }