H. Bro Thinks He's Him

H. Bro Thinks He's Him

Skibidus thinks he's Him! He proved it by solving this difficult task. Can you also prove yourself?

Given a binary string$^{\text{∗}}$ $t$, $f(t)$ is defined as the minimum number of contiguous substrings, each consisting of identical characters, into which $t$ can be partitioned. For example, $f(\texttt{00110001}) = 4$ because $t$ can be partitioned as $\texttt{[00][11][000][1]}$ where each bracketed segment consists of identical characters.

Skibidus gives you a binary string $s$ and $q$ queries. In each query, a single character of the string is flipped (i.e. $\texttt{0}$ changes to $\texttt{1}$ and $\texttt{1}$ changes to $\texttt{0}$); changes are saved after the query is processed. After each query, output the sum over all $f(b)$ where $b$ is a non-empty subsequence$^{\text{†}}$ of $s$, modulo $998\,244\,353$.

$^{\text{∗}}$A binary string consists of only characters $\texttt{0}$ and $\texttt{1}$.

$^{\text{†}}$A subsequence of a string is a string which can be obtained by removing several (possibly zero) characters from the original string.

Input

The first line contains an integer $t$ ($1 \leq t \leq 10^4$) — the number of test cases.

The first line of each test case contains a binary string $s$ ($1 \leq |s| \leq 2 \cdot 10^5$).

The following line of each test case contains an integer $q$ ($1 \leq q \leq 2 \cdot 10^5$) — the number of queries.

The following line contains $q$ integers $v_1, v_2, \ldots, v_q$ ($1 \leq v_i \leq |s|$), denoting $s_{v_i}$ is flipped for the $i$'th query.

It is guaranteed that the sum of $|s|$ and the sum of $q$ over all test cases does not exceed $2 \cdot 10^5$.

Output

For each test case, output $q$ integers on a single line — the answer after each query modulo $998\,244\,353$.

Example

Input

3
101
2
1 3
10110
3
1 2 3
101110101
5
7 2 4 4 1

Output

10 7 
61 59 67 
1495 1169 1417 1169 1396 

Note

In the first test case, $s$ becomes $\texttt{001}$ after the first query. Let's calculate the answer for each subsequence:

  • $f(s_1) = f(\texttt{0}) = 1$
  • $f(s_2) = f(\texttt{0}) = 1$
  • $f(s_3) = f(\texttt{1}) = 1$
  • $f(s_1 s_2) = f(\texttt{00}) = 1$
  • $f(s_1 s_3) = f(\texttt{01}) = 2$
  • $f(s_2 s_3) = f(\texttt{01}) = 2$
  • $f(s_1 s_2 s_3) = f(\texttt{001}) = 2$

The sum of these values is $10$, modulo $998\,244\,353$.

 

解题思路

  给出一种比较简单粗暴的 DDP 做法。

  对于统计类题目尤其是涉及到取模时,可以考虑贡献法加组合数学,或者动态规划。先考虑没有修改操作时字符串的答案(所有子序列 $01$ 段数量的总和),试了一下发现动态规划可做(之后还会给出贡献法的做法)。

  定义 $f(i,0/1)$ 表示前 $i$ 个字符中以 $0/1$ 结尾的子序列的 $01$ 段数量总和;$g(i,0/1)$ 表示前 $i$ 个字符中以 $0/1$ 结尾的子序列的总数。根据第 $i$ 个字符 $s_i  \in \{0,1\}$ 接到结尾是 $0/1$ 的子序列或空串进行状态转移。其中当 $s_i = 0$ 时:

\begin{cases}
f(i,0) = 2 \cdot f(i-1,0) + f(i-1,1) + g(i-1,1) + 1 \\
g(i,0) = 2 \cdot g(i-1,0) + g(i-1,1) + 1 \\
f(i,1) = f(i-1,1) \\
g(i,1) = g(i-1,1)
\end{cases}

  当 $s_i = 1$ 时:

\begin{cases}
f(i,0) = f(i-1,0) \\
g(i,0) = g(i-1,1) \\
f(i,1) = 2 \cdot f(i-1,1) + f(i-1,0) + g(i-1,0) + 1 \\
g(i,1) = 2 \cdot g(i-1,1) + g(i-1,0) + 1
\end{cases}

  最后整个字符串 $s$ 的答案就是 $f(n,0) + f(n,1)$。

  当动态规划涉及到修改操作时,就要考虑用矩阵来维护状态转移。因为会用到线段树来维护矩阵的乘积,修改操作只需修改矩阵,而不需要重新 dp。定义

$$F_i = \begin{bmatrix} f(i,0) & f(i,1) & g(i,0) & g(i,1) & 1 \end{bmatrix}, \quad G_0 = \begin{bmatrix} 2 & 0 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 \\ 0 & 0 & 2 & 0 & 0 \\ 1 & 0 & 1 & 1 & 0 \\ 1 & 0 & 1 & 0 & 1\end{bmatrix}, \quad G_1 = \begin{bmatrix} 1 & 1 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 & 0 \\ 0 & 0 & 0 & 2 & 0 \\ 0 & 1 & 0 & 1 & 1\end{bmatrix}$$

  那么当 $s_i = 0$ 时,上述的状态转移方程就可以表示为 $F_i = F_{i-1} \times G_0$。当 $s_i = 1$ 时,状态转移方程就可以表示为 $F_i = F_{i-1} \times G_1$。

  因此有 $F_n = F_0 \times \prod\limits_{i=1}^{n}G_{s_i}$,其中 $F_0 = \begin{bmatrix} 0 & 0 & 0 & 0 & 1 \end{bmatrix}$。

  用线段树去维护区间 $[l,r]$ 矩阵乘积的结果,即 $\prod\limits_{i=l}^{r}G_{s_i}$。那么当修改第 $x$ 个字符时,需要在线段树上把区间 $[x,x]$ 的矩阵修改成另外一个矩阵即可。

  最后答案就是 $F_n[0][0] + F_n[0][1]$。

  AC 代码如下,时间复杂度为 $O\left(5^3(n + q\log{n})\right)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5, mod = 998244353;

char s[N];
struct Matrix {
    array<array<int, 5>, 5> a;
    
    Matrix(array<array<int, 5>, 5> b = {0}) {
        a = b;
    }
    auto& operator[](int x) {
        return a[x];
    }
    Matrix operator*(Matrix b) {
        Matrix c;
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                for (int k = 0; k < 5; k++) {
                    c[i][j] = (c[i][j] + 1ll * a[i][k] * b[k][j]) % mod;
                }
            }
        }
        return c;
    }
}g[2];
struct Node {
    int l, r;
    Matrix f;
}tr[N * 4];

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        tr[u].f = g[s[l] & 1];
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u].f = tr[u << 1].f * tr[u << 1 | 1].f;
    }
}

void modify(int u, int x) {
    if (tr[u].l == tr[u].r) {
        tr[u].f = g[s[x] & 1];
    }
    else {
        if (x <= tr[u].l + tr[u].r >> 1) modify(u << 1, x);
        else modify(u << 1 | 1, x);
        tr[u].f = tr[u << 1].f * tr[u << 1 | 1].f;
    }
}

void solve() {
    int n, m;
    cin >> s >> m;
    n = strlen(s);
    memmove(s + 1, s, n + 1);
    g[0] = Matrix({
        2, 0, 0, 0, 0,
        1, 1, 0, 0, 0,
        0, 0, 2, 0, 0,
        1, 0, 1, 1, 0,
        1, 0, 1, 0, 1
    });
    g[1] = Matrix({
        1, 1, 0, 0, 0,
        0, 2, 0, 0, 0,
        0, 1, 1, 1, 0,
        0, 0, 0, 2, 0,
        0, 1, 0, 1, 1
    });
    build(1, 1, n);
    Matrix f({0, 0, 0, 0, 1});
    while (m--) {
        int x;
        cin >> x;
        s[x] ^= 1;
        modify(1, x);
        Matrix t = f * tr[1].f;
        cout << (t[0][0] + t[0][1]) % mod << ' ';
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

  再给出官方题解的做法。考虑段数通过哪些贡献得到的,容易知道在 $01$ 串中,如果存在相邻两个字符不同,那么段数就会加 $1$。因此在原串 $s$ 中,如果存在 $s_i \ne s_j$,则对答案的贡献就是以这两个字符为相邻字符的子序列的数量,即 $2^{i-1} \cdot 2^{n-j}$。

  当不涉及修改操作时,枚举 $i$,同时维护 $g_0$ 表示前缀中满足 $s_j = 0$ 的 $2^{j-1}$ 的总和;$g_1$ 表示前缀中满足 $s_j = 1$ 的 $2^{j-1}$ 的总和。那么 $s_i$ 与前缀不同的字符作为子序列相邻字符时对答案的贡献就是 $2^{n-i} \cdot g_{\neg s_i}$。时间复杂度是 $O(n)$。

  通过上面的方法可以求出原串 $s$ 的答案 $\text{ans}$。当修改 $s_x$ 时,我们只需从 $\text{ans}$ 中减去 $s_x$ 对答案的贡献即可。其中包括与前缀和后缀中的 $\neg s_x$ 相邻的两个部分。前缀部分的贡献是 $2^{n-x}\sum\limits_{i=1}^{x-1}{[s_i \ne s_x]2^{i-1}}$,后缀部分的贡献是 $2^{x-1}\sum\limits_{i=x+1}^{n}{[s_i \ne s_x]2^{n-i}}$。然后再对 $\text{ans}$ 加上修改后的贡献 $2^{n-x}\sum\limits_{i=1}^{x-1}{[s_i = s_x]2^{i-1}}$ 以及 $2^{x-1}\sum\limits_{i=x+1}^{n}{[s_i = s_x]2^{n-i}}$(此时还没对 $s_x$ 进行修改)。由于涉及到修改操作,因此我们用树状数组来分别维护 $0/1$ 关于 $2^{i-1}$ 和 $2^{n-i}$ 的前缀和。

  AC 代码如下,时间复杂度为 $O((n+q)\log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 2e5 + 5, mod = 998244353;

int n, m;
char s[N];
int p[N];
int tr1[2][N], tr2[2][N];

int lowbit(int x) {
    return x & -x;
}

void add(int *tr, int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) {
        tr[i] = (tr[i] + c) % mod;
    }
}

int query(int *tr, int x) {
    int ret = 0;
    for (int i = x; i; i -= lowbit(i)) {
        ret = (ret + tr[i]) % mod;
    }
    return ret;
}

void solve() {
    cin >> s >> m;
    n = strlen(s);
    memmove(s + 1, s, n + 1);
    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = 2ll * p[i - 1] % mod;
    }
    memset(tr1[0], 0, n + 1 << 2);
    memset(tr1[1], 0, n + 1 << 2);
    memset(tr2[0], 0, n + 1 << 2);
    memset(tr2[1], 0, n + 1 << 2);
    int ret = p[n] - 1; // 每个子序列至少有一段
    for (int i = 1; i <= n; i++) {
        add(tr1[s[i] & 1], i, p[i - 1]);
        add(tr2[s[i] & 1], i, p[n - i]);
        ret = (ret + 1ll * p[n - i] * query(tr1[~s[i] & 1], i - 1)) % mod;
    }
    while (m--) {
        int x;
        cin >> x;
        ret = (ret - 1ll * p[n - x] * query(tr1[~s[x] & 1], x - 1) - p[x - 1] * LL(query(tr2[~s[x] & 1], n) - query(tr2[~s[x] & 1], x))) % mod;
        ret = (ret + 1ll * p[n - x] * query(tr1[s[x] & 1], x - 1) + p[x - 1] * LL(query(tr2[s[x] & 1], n) - query(tr2[s[x] & 1], x))) % mod;
        ret = (ret + mod) % mod;
        cout << ret << ' ';
        add(tr1[s[x] & 1], x, -p[x - 1]);
        add(tr1[~s[x] & 1], x, p[x - 1]);
        add(tr2[s[x] & 1], x, -p[n - x]);
        add(tr2[~s[x] & 1], x, p[n - x]);
        s[x] ^= 1;
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  Codeforces Round 1003 (Div. 4) Editorial:https://codeforces.com/blog/entry/139178

posted @ 2025-02-10 03:27  onlyblues  阅读(113)  评论(0)    收藏  举报
Web Analytics