P6864 RC-03 记忆
设当前括号串 \(S\) 中:
- 合法括号非空 子串 数量为 \(\mathrm{ans}\)。
- 合法括号非空 后缀子串 数量为 \(\mathrm{cnt}\)。
例: \(S = \mathtt{()()}\),则 \(\mathrm{ans} = 3\),\(\mathrm{cnt} = 2\)。
设 \(S\) 进行一步操作一后,变为括号串 \(S_1 = \overline{S\mathtt{()}}\),相应参数变为 \(\mathrm{ans}_1\) 和 \(\mathrm{cnt}_1\)。
现试图找到 \(\mathrm{ans_1}\) 和 \(\mathrm{ans}\)、\(\mathrm{cnt_1}\) 和 \(\mathrm{cnt}\) 的关系。
不难发现 \(\mathrm{cnt_1 = cnt + 1}\),\(\mathrm{ans_1 = ans + cnt + 1}\)。
再设 \(S\) 进行一步操作二后,变为括号串 \(S_2 = \overline{\mathtt ( S\mathtt)}\),相应参数 \(\mathrm{ans_2}\) 和 \(\mathrm{cnt_2}\)。
我们试图证明,新增的合法括号非空子串只有 \(\boldsymbol{S_2}\) 本身一个。
反证法,我们考虑有没有可能,新加的这对括号中的左括号,和 \(S\) 的一段前缀组成一段合法括号子串?
很明显,这要求 \(S\) 的这段前缀中,右括号比左括号多一个。
但在操作的变换中,字符串始终是合法括号串。因此,\(S\) 不可能存在一段右括号比左括号多的前缀。
那有无可能,新加的这对括号中的右括号,和 \(S\) 的一段后缀组成一段合法括号子串?
这要求 \(S\) 的这段后缀里,左括号比右括号多一个。
可以发现,这就意味着 \(S\) 除去这段后缀的剩余那部分前缀,右括号比左括号多一个(因为整个括号串左右括号数相等),所以同样不可能。
那么新增的合法括号子串只有 \(S_2\) 本身一个咯。
所以 \(\mathrm{cnt_2} = 1\),\(\mathrm{ans_2} = \mathrm{ans} + 1\)。
现在考虑撤销操作。发现撤销操作对 \(\mathrm{cnt}\) 和 \(\mathrm{ans}\) 的变化并不平凡。
然后这里有一个比较不朴素的想法……那就是用矩阵刻画上面两种操作的变换。
刻画还是较为容易的:
那么答案可以看做初始矩阵 \(\begin{bmatrix}1 & 1 & 1\end{bmatrix}\) 经过一系列矩阵乘法得到的结果。
那撤销是什么?题目的撤销还可以撤销一个撤销操作,看起来很高级,但它的影响最后无非都是:
- 将某个非撤销操作(即操作一或操作二)从进行改为不进行。
- 将某个非撤销操作(即操作一或操作二)从不进行改为进行。
不进行操作也是可以刻画出矩阵的,即单位矩阵 \(\begin{bmatrix}1 & 0 & 0 \\0 & 1 & 0 \\ 0 & 0 & 1\end{bmatrix}\)。
所以任何一个撤销操作都是将一个非撤销操作对应的矩阵单点修改,要么是从操作矩阵修改成单位矩阵,要么是从单位矩阵修改成操作矩阵。
因此现在变成一个对矩阵单点修改,求矩阵全局乘法的问题。因为矩阵乘法有结合律,可以用线段树维护。
因为矩阵乘法没法差分,所以不能用单 \(\log\) 的树状数组。
时间复杂度 \(\Theta(K^3 n \log n)\),其中 \(K = 3\)。
/*
* @Author: crab-in-the-northeast
* @Date: 2023-05-15 16:06:19
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-05-15 19:15:26
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
struct phalanx {
int a[4][4];
phalanx () {
for (int i = 1; i <= 3; ++i)
for (int j = 1; j <= 3; ++j)
a[i][j] = 0;
}
phalanx (std :: vector <int> v) {
for (int i = 1; i <= 3; ++i)
for (int j = 1; j <= 3; ++j)
a[i][j] = v[(i - 1) * 3 + j - 1];
}
const phalanx operator * (const phalanx b) const {
phalanx ans;
for (int i = 1; i <= 3; ++i)
for (int j = 1; j <= 3; ++j)
for (int k = 1; k <= 3; ++k)
ans.a[i][j] += a[i][k] * b.a[k][j];
return ans;
}
};
const phalanx UNIT({1, 0, 0, 0, 1, 0, 0, 0, 1}),
OP1({1, 0, 0, 1, 1, 0, 1, 1, 1}),
OP2({1, 0, 0, 0, 0, 0, 1, 1, 1});
const int N = (int)2e5 + 5;
struct node {
int l, r;
phalanx ans;
} t[N << 2];
inline node con(node lef, node rgt) {
return (node){
l: lef.l,
r: rgt.r,
ans: lef.ans * rgt.ans
};
}
inline void up(int p) {
t[p] = con(t[ls(p)], t[rs(p)]);
}
void build(int p, int l, int r) {
if (l == r) {
t[p].l = t[p].r = l;
t[p].ans = UNIT;
return ;
}
int mid = (l + r) >> 1;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
up(p);
}
void modify(int p, int x, int op) {
int l = t[p].l, r = t[p].r;
if (l == r) {
if (op == 1)
t[p].ans = OP1;
else if (op == 2)
t[p].ans = OP2;
else
t[p].ans = UNIT;
return ;
}
int mid = (l + r) >> 1;
if (x <= mid)
modify(ls(p), x, op);
else
modify(rs(p), x, op);
up(p);
}
int ops[N];
int pos[N];
signed main() {
int n = read();
build(1, 1, n);
for (int i = 1; i <= n; ++i) {
int op = ops[i] = read();
pos[i] = i;
if (op <= 2)
modify(1, i, op);
else {
int p = read();
p = pos[i] = pos[p];
ops[p] = -ops[p];
modify(1, p, ops[p]);
}
phalanx ans = t[1].ans;
printf("%lld\n", ans.a[1][1] + ans.a[2][1] + ans.a[3][1]);
}
return 0;
}