Loading

2.11 CW 模拟赛 T2. 刀言刀语

思路

很像大模拟

赛时想法

类似括号树的结构, 然后合并树上点可以很快地做出来

但是好像并不好写, 所以还是写正常做法

无撤销操作

首先考虑只有 1,21, 2 操作, 侧重于计算合法括号子串的个数
考虑每一次变化的时候是简单操作,
记录一下之前的 ansans 和最外层括号的数量 cntcnt

对于操作 11, 直接 ansans+cnt,cntcnt+1ans \gets ans + cnt, cnt \gets cnt + 1 即可
而对于操作 22, 直接 ansans+1,cnt1ans \gets ans + 1, cnt \gets 1 即可

证明
  • 操作 11
    在最右侧加入一个 () , 可以和左边所有的同级括号组成新的括号子串, 也就是产生 cntcnt 个新的合法括号子串
    然后 cntcnt+1cnt \gets cnt + 1 不需要多说
  • 操作 22
    这种情况下同级括号一定清零, 对答案的影响只有 11 , 非常显然

考虑撤销操作
你发现如果仅仅对操作 \(1, 2\) 撤销还比较简单, 用一个矩阵线段树来维护每一次操作, 撤销就是变成单位矩阵

\[\begin{bmatrix} ans\\ cnt\\ 1 \end{bmatrix} \to \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

但是如果撤销 \(3\) 操作怎么办

我们考虑最后撤销的形式

\[\textrm{op3} \to \textrm{op3} \to \textrm{op3} \to \textrm{op3} \to \cdots \to \begin{cases} \textrm{op1} \\ \textrm{op2} \end{cases} \]

找一下规律
你发现撤销一次等效于

\[\to \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} \]

撤销两次等效于再加回去

我们考虑维护奇偶性, 然后每次 \(3\) 操作直接对应操作即可

实现

#include <bits/stdc++.h>

const int MAXN = 2e5 + 20;

int op[MAXN]; // 操作类型
int pos[MAXN]; // 操作位置
int list[MAXN]; // 记录 i 操作的深度

struct matrix {
    long long a[3][3];
    matrix operator * (matrix b) {
        matrix c;
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2; j++) {
                c.a[i][j] = 0;
                for (int k = 0; k <= 2; k++) {
                    c.a[i][j] += a[i][k] * b.a[k][j];
                }
            }
        }
        return c;
    }
};
matrix OP1, OP2, I; // 操作
matrix P; // 答案矩阵

class segmenttree
{
private:

public:
    matrix tree[MAXN << 2];
    void build(int l, int r, int rt) {
        int mid = (l + r) >> 1;
        tree[rt] = I;
        if (l == r) return;
        build(l, mid, rt << 1);
        build(mid + 1, r, rt << 1 | 1);
        tree[rt] = tree[rt << 1] * tree[rt << 1 | 1];
    }
    void update(int aim, int type, int l, int r, int rt)
    {
        int mid = (l + r) >> 1;
        if (l == r) {
            if (type == 1) tree[rt] = OP1;
            else if (type == 2) tree[rt] = OP2;
            else tree[rt] = I;
            return;
        }
        if (aim <= mid) update(aim, type, l, mid, rt << 1);
        else update(aim, type, mid + 1, r, rt << 1 | 1);
        tree[rt] = tree[rt << 1] * tree[rt << 1 | 1];
    }
} seg;
int main()
{
    int n, x;
    scanf("%d", &n);
    OP1.a[0][0] = OP1.a[1][0] = OP1.a[1][1] = OP1.a[2][0] = OP1.a[2][1] = OP1.a[2][2] = OP2.a[0][0] = OP2.a[2][0] = OP2.a[2][1] = OP2.a[2][2] = I.a[0][0] = I.a[1][1] = I.a[2][2] = P.a[0][0] = P.a[0][1] = P.a[0][2] = 1;
    seg.build(1, n, 1);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &op[i]);
        if (op[i] <= 2) {
            pos[i] = i;
            seg.update(i, op[i], 1, n, 1);
        }
        else {
            scanf("%d", &x);
            pos[i] = pos[x];
            list[i] = (list[x] == 1 ? 0 : 1), op[i] = op[x];
            seg.update(pos[i], (list[i] == 1) ? 3 : op[pos[i]], 1, n, 1);
        }
        printf("%lld\n", (P * seg.tree[1]).a[0][0]);
    }
    return 0;
}

总结

处理合法括号串子序列的特定方法, 方便一个一个加入的时候处理

对子串类问题的理解 $a = b + c$

子串是从前后删除一段得到的, 所以我们可以 O(n)\mathcal{O} (n) 扫描串作为结尾, 简化成了只需要删除前缀

一类迭代转移类问题, 修改利用矩阵的题目

嵌套操作找规律, 不能直接模拟深度, 最一般的是使用并查集这样的数据结构

posted @ 2025-02-11 21:01  Yorg  阅读(29)  评论(0)    收藏  举报