D ★★飞马祝福语★★
现设当前串为 s s s ,目标串为 g o a l goal goal
方法一:
将 d p dp dp 转移转化为矩阵转移,利用线段树维护矩阵区间乘积
设 d p [ i ] [ j ] dp[i][j] dp[i][j] 含义为用 s s s 串的前 i i i 个字母(下标从 1 1 1 开始),最多能组成多少个与 g o a l [ 0 : j ] goal[0:j] goal[0:j] 相同的子序列。
d
p
[
i
]
[
0
]
=
1
(
空
串
)
dp[i][0] = 1(空串)
dp[i][0]=1(空串)
d
p
[
0
]
[
j
]
=
0
(
j
≠
0
)
dp[0][j] = 0(j≠0)
dp[0][j]=0(j=0)
然后分两种情况:
(1)
s
[
i
]
=
=
g
o
a
l
[
j
]
:
:
:
:
:
:
:
>
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
1
]
s[i] == goal[j]:::::::> dp[i][j] = dp[i-1][j] + dp[i-1][j -1]
s[i]==goal[j]:::::::>dp[i][j]=dp[i−1][j]+dp[i−1][j−1]
(2)
s
[
i
]
!
=
g
o
a
l
[
j
]
:
:
:
:
:
:
:
>
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
s[i] \ \ != goal[j]:::::::>dp[i][j]=dp[i-1][j]
s[i] !=goal[j]:::::::>dp[i][j]=dp[i−1][j]
我们设矩阵:
D [ i ] = [ d p [ i ] [ 0 ] d p [ i ] [ 1 ] d p [ i ] [ 2 ] d p [ i ] [ 3 ] d p [ i ] [ 4 ] d p [ i ] [ 5 ] ] D[i] = \left[ \begin{matrix} dp[i][0] & dp[i][1] & dp[i][2] & dp[i][3] & dp[i][4] & dp[i][5]\ \end{matrix} \right] D[i]=[dp[i][0]dp[i][1]dp[i][2]dp[i][3]dp[i][4]dp[i][5] ]
①倘若
s
[
i
]
s[i]
s[i] 不等于
F
e
i
M
a
FeiMa
FeiMa 中的任何字符:
D
[
i
]
=
D
[
i
−
1
]
[
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
]
D[i] =D[i-1] \left[ \begin{matrix} 1 & 0 & 0 & 0 & 0 & 0\\0 & 1 & 0 & 0 & 0 & 0\\0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 0 & 0 \\0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1\end{matrix} \right]
D[i]=D[i−1]⎣⎢⎢⎢⎢⎢⎢⎡100000010000001000000100000010000001⎦⎥⎥⎥⎥⎥⎥⎤
②倘若
s
[
i
]
=
=
F
s[i] == F
s[i]==F:
D
[
i
]
=
D
[
i
−
1
]
[
1
1
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
]
D[i] =D[i-1] \left[ \begin{matrix} 1 & 1 & 0 & 0 & 0 & 0\\0 & 1 & 0 & 0 & 0 & 0\\0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 0 & 0 \\0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1\end{matrix} \right]
D[i]=D[i−1]⎣⎢⎢⎢⎢⎢⎢⎡100000110000001000000100000010000001⎦⎥⎥⎥⎥⎥⎥⎤
③倘若
s
[
i
]
=
=
F
s[i] == F
s[i]==F:
D
[
i
]
=
D
[
i
−
1
]
[
1
0
0
0
0
0
0
1
1
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
0
0
0
1
]
D[i] =D[i-1] \left[ \begin{matrix} 1 & 0 & 0 & 0 & 0 & 0\\0 & 1 & 1 & 0 & 0 & 0\\0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 0 & 0 \\0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1\end{matrix} \right]
D[i]=D[i−1]⎣⎢⎢⎢⎢⎢⎢⎡100000010000011000000100000010000001⎦⎥⎥⎥⎥⎥⎥⎤
而后以此类推。
整体思路就是依据原字符串根据以上六种转移矩阵建立一颗维护矩阵区间乘积的线段树,更新的话就可以预处理出这六种矩阵的 n n n 次方,进行区间更新。其实并不需要写询问函数,我们发现每次询问的都是根节点,直接拿出来用就好了。整体复杂度 O ( 6 3 q l o g n ) O(6^3 q logn) O(63qlogn)
注意按理我们是要初始化 D [ 0 ] D[0] D[0] 的,最后答案就是 D [ 0 ] ∗ t r e e [ 1 ] D[0]*tree[1] D[0]∗tree[1] 的右上角那个点的数值,但是
D
[
0
]
=
[
1
0
0
0
0
0
]
D[0] = \left[ \begin{matrix} 1 & 0 & 0 & 0 & 0 & 0\ \end{matrix} \right]
D[0]=[100000 ]
所以无论乘或不乘
D
[
0
]
D[0]
D[0] 右上角的值不会改变,答案就直接是矩阵
t
r
e
e
[
1
]
tree[1]
tree[1] 右上角那个值
#include <bits/stdc++.h>
#define lson x<<1
#define rson (x<<1)|1
using namespace std;
typedef long long ll;
const int N = 1e5 + 2;
const int mod = 998244353;
const int len = 6;
char s[N], tmp[N];
int id[200], n, q, lazy[N << 2], a[N];
struct matrix {
int m[6][6];
matrix() {
memset(m, 0, sizeof m);
}
};
matrix org, pt[7][N], tree[N << 2], one;
matrix mul(matrix a, matrix b) {
matrix ans;
for (int i = 0; i < len; ++i) {
for (int j = 0; j < len; ++j) {
for (int k = 0; k < len; ++k) {
ans.m[i][j] = (ans.m[i][j] + 1ll * a.m[i][k] * b.m[k][j]) % mod;
}
}
}
return ans;
}
void init() {
for (int i = 0; i < 6; ++i) one.m[i][i] = 1;
id['F'] = 1, id['e'] = 2, id['i'] = 3, id['M'] = 4, id['a'] = 5;
org.m[0][0] = 1;
for (int i = 0; i < 6; ++i) {
for (int j = 0; j < 6; ++j) {
pt[i][1].m[j][j] = 1;
}
}
for (int i = 1; i < 6; ++i) pt[i][1].m[i - 1][i] = 1;
for (int i = 0; i < 6; ++i) {
for (int j = 2; j <= 100000; ++j) {
pt[i][j] = mul(pt[i][j - 1], pt[i][1]);
}
}
}
void build(int x, int tl, int tr) {
if (tl == tr) {
tree[x] = pt[a[tl]][1];
lazy[x] = -1;
return ;
}
int mid = tl + tr >> 1;
build(lson, tl, mid);
build(rson, mid + 1, tr);
tree[x] = mul(tree[lson], tree[rson]);
lazy[x] = -1;
}
void push_down(int x, int tl, int tr) {
if (lazy[x] == -1) return ;
lazy[lson] = lazy[rson] = lazy[x];
int mid = tl + tr >> 1;
tree[lson] = pt[lazy[x]][mid - tl + 1];
tree[rson] = pt[lazy[x]][tr - mid];
lazy[x] = -1;
}
void update(int x, int tl, int tr, int l, int r, int tp) {
if (l <= tl && tr <= r) {
tree[x] = pt[tp][tr - tl + 1];
lazy[x] = tp;
return ;
}
push_down(x, tl, tr);
int mid = tl + tr >> 1;
if (mid >= l) update(lson, tl, mid, l, r, tp);
if (mid < r) update(rson, mid + 1, tr, l, r, tp);
tree[x] = mul(tree[lson], tree[rson]);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
init();
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d%s", &n, &q, s);
for (int i = 0; i < n; ++i) {
a[i + 1] = id[s[i]];
}
build(1, 1, n);
while(q--) {
int l, r, tp;
scanf("%d%d%s", &l, &r, tmp);
tp = id[tmp[0]];
update(1, 1, n, l, r, tp);
printf("%d\n", tree[1].m[0][5]);
}
}
}
方法二:
利用线段树维护区间 d p dp dp
我们设 t r e e [ r t ] [ i ] [ j ] tree[rt][i][j] tree[rt][i][j] 表示在线段树 r t rt rt 号对应的区间内包含 g o a l [ i : j ] goal[i: j] goal[i:j] 的子序列的数量,那么我们就可以得到转移方程:
t r e e [ r t ] [ i ] [ j ] = t r e e [ r t < < 1 ] [ i ] [ j ] + t r e e [ r t < < 1 ∣ 1 ] [ i ] [ j ] + ∑ k = i j − 1 t r e e [ r t < < 1 ] [ i ] [ k ] + t r e e [ r t < < 1 ∣ 1 ] [ k + 1 ] [ j ] tree[rt][i][j] = tree[rt<<1][i][j]+tree[rt<<1|1][i][j]+\sum_{k=i}^{j-1}tree[rt<<1][i][k]+tree[rt<<1|1][k+1][j] tree[rt][i][j]=tree[rt<<1][i][j]+tree[rt<<1∣1][i][j]+∑k=ij−1tree[rt<<1][i][k]+tree[rt<<1∣1][k+1][j]
也就是线段树的 p u s h _ u p push\_up push_up 就是上面这个转移方程,我们只要以区间 d p dp dp 的顺序去建树,去更新值,就可以得到正确的值。其答案就是 t r e e [ 1 ] [ 1 ] [ 5 ] tree[1][1][5] tree[1][1][5] 。整体复杂度 O ( 5 3 q l o g n ) O(5^3qlogn) O(53qlogn)
#include <bits/stdc++.h>
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int N = 1e5 + 2;
const int mod = 998244353;
int n, q;
int tree[N << 2][5][5], lazy[N << 2][5][5];
char s[N], goal[10] = " FeiMa";
void push_up(int x, int I, int J) {
int ans = (tree[lson][I][J] + tree[rson][I][J]) % mod;
for (int i = I; i < J; ++i) {
ans = (ans + 1ll * tree[lson][I][i] * tree[rson][i + 1][J]) % mod;
}
tree[x][I][J] = ans;
}
void push_down(int x, int tl, int tr, int I, int J) {
if (~lazy[x][I][J]) {
int mid = tl + tr >> 1;
lazy[lson][I][J] = lazy[rson][I][J] = lazy[x][I][J];
tree[lson][I][J] = lazy[x][I][J] * (mid - tl + 1);
tree[rson][I][J] = lazy[x][I][J] * (tr - mid);
lazy[x][I][J] = -1;
}
}
void update(int x, int tl, int tr, int l, int r, int I, int J, char c) {
if (l <= tl && tr <= r) {
if (I == J && goal[I] == c) {
tree[x][I][J] = tr - tl + 1;
lazy[x][I][J] = 1;
}
else {
tree[x][I][J] = 0;
lazy[x][I][J] = 0;
}
return ;
}
push_down(x, tl, tr, I, J);
int mid = tl + tr >> 1;
if (mid >= l) update(lson, tl, mid, l, r, I, J, c);
if (mid < r) update(rson, mid + 1, tr, l, r, I, J, c);
push_up(x, I, J);
}
void build(int x, int tl, int tr, int I, int J) {
lazy[x][I][J] = -1;
if (tl == tr) {
if (I ^ J) tree[x][I][J] = 0;
else if (s[tl] ^ goal[I]) tree[x][I][J] = 0;
else tree[x][I][J] = 1;
return ;
}
int mid = tl + tr >> 1;
build(lson, tl, mid, I, J);
build(rson, mid + 1, tr, I, J);
push_up(x, I, J);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int T;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &q);
scanf("%s", s + 1);
for (int len = 1; len <= 5; ++len) {
for (int s = 1; s + len - 1 <= 5; ++s) {
build(1, 1, n, s, s + len - 1);
}
}
while(q--) {
int l, r;
char op[2];
scanf("%d%d%s", &l, &r, op);
for (int len = 1; len <= 5; ++len) {
for (int s = 1; s + len - 1 <= 5; ++s) {
update(1, 1, n, l, r, s, s + len - 1, op[0]);
}
}
printf("%d\n", tree[1][1][5]);
}
}
}