兰州大学第一届『飞马杯』D ★★飞马祝福语★★

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]=0j=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[i1][j]+dp[i1][j1]
(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[i1][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[i1]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[i1]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[i1]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<<11][i][j]+k=ij1tree[rt<<1][i][k]+tree[rt<<11][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]);
		}
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值