2.11 CW 模拟赛 T2. 刀言刀语
思路
很像大模拟
赛时想法
类似括号树的结构, 然后合并树上点可以很快地做出来
但是好像并不好写, 所以还是写正常做法
无撤销操作
首先考虑只有 操作, 侧重于计算合法括号子串的个数
考虑每一次变化的时候是简单操作,
记录一下之前的 和最外层括号的数量
对于操作 , 直接 即可
而对于操作 , 直接 即可
证明
- 操作
在最右侧加入一个(), 可以和左边所有的同级括号组成新的括号子串, 也就是产生 个新的合法括号子串
然后 不需要多说 - 操作
这种情况下同级括号一定清零, 对答案的影响只有 , 非常显然
考虑撤销操作
你发现如果仅仅对操作 \(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$
子串是从前后删除一段得到的, 所以我们可以 扫描串作为结尾, 简化成了只需要删除前缀
一类迭代转移类问题, 修改利用矩阵的题目
嵌套操作找规律, 不能直接模拟深度, 最一般的是使用并查集这样的数据结构

浙公网安备 33010602011771号