「atcoder - abc246h」01? Queries
平时基本打不到 ex,这个 ex 还是比较 ez 的,但也有些需要注意的地方。
考虑 dp 规划前缀,设 \(f[i][0/1]\) 表示前缀 \([1, i]\) 否是选 \(i\) 的方案数,这个明显会算重,注意到前缀 \([1, i)\) 凑不出来的字符串可以通过前缀 \([1, i)\) 再 append 一个 \(0/1\) 凑出来,所以我们改描述 \(f[i][0/1]\) 为前缀 \([1, i]\) 凑出结尾为 \(\texttt0/\texttt1\) 的方案数。
转移分 \(s_i\) 是 \(\texttt{0}/\texttt{1}/\texttt{?}\) 讨论,放到矩阵上做 ddp 即可,如果令 \(f[0][0] = f[0][1] := 1\) 可以缩减矩阵的规模至 \(2\),但是这样答案就要减 \(2\)。
这类 dp 通常对当前元素的直接规划会算重,可以将目光放到已经构造出的结果上,再结合结论(其实比较 ad hoc 了)避免算重。
// s[i]=0: dp[i][0] = dp[i-1][0]+dp[i-1][1], dp[i][1] = dp[i-1][1]
// s[i]=1: dp[i][0] = dp[i-1][0], dp[i][1] = dp[i-1][0]+dp[i-1][1]
// s[i]=?: dp[i][0/1] = dp[i-1][0]+dp[i-1][1]
using modint = modint998244353;
using mt = static_matrix<modint, 2>;
int n, q, ms, mh;
char s[100100];
vi<mt> md;
mt get(int i) {
return mt({{1, s[i-1] == '1' || s[i-1] == '?'}, {s[i-1] == '0' || s[i-1] == '?', 1}});
}
void upd(int now) {
md[now] = md[now*2]*md[now*2+1];
}
void mdf(int now, const mt& w) {
md[now += ms-1] = w;
for (int i=1; i<=mh; ++i) {
upd(now>>i);
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> q >> s;
mh = ceil(log2(n)), ms = 1<<mh;
md = vi<mt>(ms*2, mt::id());
for (int i=1; i<=n; ++i) {
md[i+ms-1] = get(i);
}
for (int i=ms-1; i>=1; --i) {
upd(i);
}
char c;
for (int x; q--;) {
cin >> x >> c;
s[x-1] = c;
mdf(x, get(x));
mt ret = mt({{1, 1}, {0, 0}})*md[1];
cout << (ret[0][0]+ret[0][1]-2).val() << "\n";
}
}