Loading

1.9 CW 模拟赛 T3. string

思路

首先不考虑修改, 询问怎么做?

考虑 \(\rm{dp}\)
\(f_{i, 0/1}\) 表示 \(T[1:i]\) 中以 \(0/1\) 结尾的字符串的合法子序列数量
转移是容易的

\[\begin{cases} \begin{align*} & T_i = 0, \begin{cases} f_{i, 0} \gets f_{i - 1, 0} + f_{i - 1, 1} \\ f_{i, 1} \gets f_{i - 1, 1} \end{cases} \\ & T_i = 1, \begin{cases} f_{i, 1} \gets f_{i - 1, 0} + f_{i - 1, 1} + 1 \\ f_{i, 0} \gets f_{i - 1, 0} \end{cases} \\ & T_i = \_, \begin{cases} f_{i, 1} \gets f_{i - 1, 1} \\ f_{i, 0} \gets f_{i - 1, 0} \end{cases} \\ \end{align*} \end{cases} \]

容易发现这 \(3\) 类转移都可以用矩阵刻画, 具体的, 设计状态矩阵为

\[\begin{pmatrix} f_{i, 0} \\ f_{i, 1} \\ 1 \end{pmatrix} \]

\[\begin{cases} \begin{align*} & T_i = 0, \begin{pmatrix} f_{i, 0} \\ f_{i, 1} \\ 1 \end{pmatrix} \gets \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{pmatrix} f_{i - 1, 0} \\ f_{i - 1, 1} \\ 1 \end{pmatrix} \\ & T_i = 1, \begin{pmatrix} f_{i, 0} \\ f_{i, 1} \\ 1 \end{pmatrix} \gets \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} \begin{pmatrix} f_{i - 1, 0} \\ f_{i - 1, 1} \\ 1 \end{pmatrix} \\ & T_i = \_, \begin{pmatrix} f_{i, 0} \\ f_{i, 1} \\ 1 \end{pmatrix} \gets \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{pmatrix} f_{i - 1, 0} \\ f_{i - 1, 1} \\ 1 \end{pmatrix} \\ \end{align*} \end{cases} \]

不修的时间复杂度达到了 \(\mathcal{O} (\eta q \log n)\) , 其中 \(\eta = 3^3\) , 是矩乘的复杂度
考虑带修
引入 矩阵线段树

简单的处理即可

实现

作为全新的码力检验, 还是打一下

框架

线段树同线段树, 矩阵同矩阵即可
维护的细节不再赘述, 有一点特别注意:
对于 \(\rm{lazy \ tag}\) 的实现, 稍稍特殊

特别需要注意的是, 转移柿子中是状态矩阵右乘转移矩阵, 所以在线段树的实现上要注意顺序的问题, 不要写成左乘了

代码

代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20;
const int MOD = 998244353;
namespace calc {
    int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
    int mul(int a, int b) { return a * b % MOD; }
    int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
    void addon(int &a, int b) { a = add(a, b); }
    void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;

int n, q;
std::string S;

/*矩阵操作*/
struct matrix {
    int M[4][4];
    int x, y; // 规定 x 放 3 , y 确定为 1/3

    /*设为单位矩阵*/ void setI() { for (int i = 1; i <= 3; i++) M[i][i] = 1; }
    /*清空*/ void clear() { for (int i = 1; i <= 3; i++) for (int j = 1; j <= 3; j++) M[i][j] = 0; }
} I, A, B, C;
matrix operator * (matrix a, matrix b) {
    matrix ans; ans.clear();
    for (int i = 1; i <= a.x; i++) for (int j = 1; j <= b.y; j++) for (int k = 1; k <= a.y; k++) addon(ans.M[i][j], mul(a.M[i][k], b.M[k][j]));
    ans.x = a.x, ans.y = b.y; return ans;
}
matrix quickpow(matrix a, int pows) {
    matrix ans; ans = I;
    while (pows) {
        if (pows & 1) ans = ans * a;
        a = a * a; pows >>= 1;
    }
    return ans;
}

std::map<char, matrix> style; // 辅助映射
/*处理转移矩阵和单位矩阵的赋值, 并且维护映射*/
void init() {
    I.clear(); I.setI(); I.x = I.y = 3;
    A.clear(); A.M[1][1] = A.M[1][2] = A.M[2][2] = A.M[3][3] = 1; A.x = A.y = 3;
    B.clear(); B.M[1][1] = B.M[2][1] = B.M[2][2] = B.M[2][3] = B.M[3][3] = 1; B.x = B.y = 3;
    C.clear(); C.setI(); C.x = C.y = 3;
    style['0'] = A, style['1'] = B, style['_'] = C;
}

/*矩阵区间乘线段树*/
class segment_tree {
private:
    struct node { matrix val; bool iscover; } Tree[MAXN << 2];

    void pushup(int p) { Tree[p].val = Tree[p << 1 | 1].val * Tree[p << 1].val; }
    void addtag(int p, int l, int r) { Tree[p].iscover = true; Tree[p].val = I; }
    void pushdown(int p, int l, int r) {
        if (Tree[p].iscover) { int mid = (l + r) >> 1; addtag(p << 1, l, mid); addtag(p << 1 | 1, mid + 1, r); Tree[p].iscover = false; }
    }

public:
    /*建树*/
    void build(int p, int l, int r) {
        Tree[p].iscover = false;
        /*叶子结点*/ if (l == r) { Tree[p].val = style[S[l]]; return; }
        int mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r);
        pushup(p);
    }

    /*更新*/
    void update(int p, int l, int r, int aiml, int aimr) {
        if (aiml <= l && r <= aimr) { addtag(p, l, r); return; }
        pushdown(p, l, r);
        int mid = (l + r) >> 1;
        if (aiml <= mid) update(p << 1, l, mid, aiml, aimr);
        if (aimr > mid) update(p << 1 | 1, mid + 1, r, aiml, aimr);
        pushup(p);
    }

    /*查询*/
    matrix query(int p, int l, int r, int aiml, int aimr) {
        if (aiml <= l && r <= aimr) return Tree[p].val;
        pushdown(p, l, r);
        int mid = (l + r) >> 1;
        matrix res = I;
        if (aimr > mid) res = res * query(p << 1 | 1, mid + 1, r, aiml, aimr);
        if (aiml <= mid) res = res * query(p << 1, l, mid, aiml, aimr);
        return res;
    }

} segtree;


signed main()
{
    init();
    scanf("%lld %lld", &n, &q);
    std::cin >> S; S = ' ' + S;
    segtree.build(1, 1, n);
    while (q--) {
        int op, l, r;
        scanf("%lld %lld %lld", &op, &l, &r);
        if (op == 1) segtree.update(1, 1, n, l, r);
        else {
            matrix ret = segtree.query(1, 1, n, l, r);
            matrix f; f.clear(); f.M[3][1] = 1; f.x = 3, f.y = 1;
            f = ret * f;
            printf("%lld\n", add(f.M[1][1], f.M[2][1]));
        }
    }

    return 0;
}

总结

一类针对于子序列和同类问题, 特殊的转移方式
具体的, 更针对当前已经有的子序列进行插入而非根据位置选择

矩阵线段树维护一类带修的矩阵问题
注意矩阵乘法没有交换律, 维护好左右顺序

posted @ 2025-01-10 08:24  Yorg  阅读(15)  评论(0)    收藏  举报