G - Wildcard Construction 解説 by en_translator
Given \(X\) and \(Y\), the set of good strings forms a regular language. Thus, there exists a deterministic finite automaton that accepts only the good strings. Once such an automaton is constructed, the problem can be solved in a total of \(O(N|S|)\) time, where \(N\) denotes the number of states in the automaton.
First, the following is a non-deterministic finite automaton that accepts only the possible sequences for \(t\):

Also, a non-deterministic finite automaton that accepts only \(X\) can be written using the following three components as follows:


Likewise, one can also construct a non-deterministic finite automaton that accepts only \(Y\). Putting together, a non-deterministic finite automaton accepting only the good strings can be constructed as:

This is a non-deterministic finite automaton with \(|X|+|Y|+2\) states. By merging the accepting states with the terminal states of the non-deterministic finite automaton that accepts only \(X\) or \(Y\), this can be turned into a non-deterministic finite automaton with \(|X|+|Y|\) states, without \(\varepsilon\)-transition.
Based on this non-deterministic finite automaton, one can construct a deterministic finite automaton, consisting of \(O(2 ^ {|X|+|Y|})\) states representing the “possible states” in the original automaton. (For details, search “powerset construction.”)
All that left is counting. The construction of the automaton costs \(O((|X|+|Y|)2 ^ {|X|+|Y|})\) or \(O(2 ^ {|X|+|Y|})\) time, and counting the good strings costs \(O(|S|2 ^ {|X|+|Y|})\) time, so the problem can be solved fast enough.
The following is sample code.
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <atcoder/modint>
int main() {
using namespace std;
using modint = atcoder::static_modint<998244353>;
string S, X, Y;
cin >> S >> X >> Y;
const auto N{size(X)}, M{size(Y)}, L{N + M};
const auto T{X + Y};
// Construct a non-deterministic finite automaton that accepts only the good strings (without an ε-transition).
// nfa[i] = {set of states transitionable by 0, set of states transitionable by 1}
vector<pair<unsigned, unsigned>> nfa(L);
for (const auto &[i, c] : T | views::enumerate) {
const auto succ{1u << ((i + 1) % L)};
if (c != '1') nfa[i].first |= succ;
if (c != '0') nfa[i].second |= succ;
}
// After reading X, one may start over X again
if (X[0] != '1') nfa[N].first |= 2;
if (X[0] != '0') nfa[N].second |= 2;
// Construct a deterministic finite automaton based on a non-deterministic finite automaton
vector<pair<unsigned, unsigned>> dfa(1u << L);
for (auto&& [i, t] : dfa | views::enumerate | views::drop(1))
t = {dfa[i & i - 1].first | nfa[countr_zero(i + 0ul)].first, dfa[i & i - 1].second | nfa[countr_zero(i + 0ul)].second};
// Counting
vector<modint> dp(1u << L), prev(1u << L);
dp[1u << N] = 1;
for (const auto c : S) {
swap(dp, prev);
ranges::fill(dp, modint{});
if (c != '1')
for (const auto& [next, v] : views::zip(dfa | views::elements<0>, prev))
dp[next] += v;
if (c != '0')
for (const auto& [next, v] : views::zip(dfa | views::elements<1>, prev))
dp[next] += v;
}
modint ans{};
// The answer is the sum for the states containing accepting states
for (const auto& [i, v] : dp | views::enumerate) if (i & (1 | 1 << N)) ans += v;
cout << ans.val() << endl;
return 0;
}