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;
}
总结
一类针对于子序列和同类问题, 特殊的转移方式
具体的, 更针对当前已经有的子序列进行插入而非根据位置选择
矩阵线段树维护一类带修的矩阵问题
注意矩阵乘法没有交换律, 维护好左右顺序

浙公网安备 33010602011771号