In mathematics you don't understand things, you just get used to them.

标签 linear algebra 下的文章

link。

平时基本打不到 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";
    }
}