「atcoder - abc246h」01? Queries

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";
    }
}
posted @ 2022-08-05 21:57  cirnovsky  阅读(51)  评论(0编辑  收藏  举报