hdu7131 Nun Heh Heh Aaaaaaaaaaa (字符序列匹配、线性dp)

字符串的匹配子序列问题

题目链接

首先区分一点

子串(Substring)就是原串中连续的一部分

子序列(Subsequnce)就是原串中任意抽出任意多个字符(可以不连续,但顺序不能改变)


在这里插入图片描述

在这里插入图片描述



根据题意可知,需要删除原串中任意个字符后,能构成多少个,以"nunhehheh"为前缀,个数任意大于零个"a"为后缀的字符序列

这个题目考虑一下,可以分为两步来求解
前找出前缀的部分,然后找后缀 a 的个数并选任意个进行排列组合

而寻找前缀和后缀的个数就是利用,前缀和 + dp 的思想来做
对于dp的分类就是线性dp

对于一种情况来考虑:假设Q = “nunhehhehaa” ,S = “nunhehheh”
我来寻找前缀中能构成他的前缀

即 “nunhehheh” 由 “nunhehhe” 构成,知道前者的个数再判断之后有几个 ‘h’ 不就可以解决前缀的个数问题了
即 “nunhehhe” 由 “nunhehh” 构成,知道前者个数和后面有几个字符 'e’即可
即 “nunhehh” 由 “nunheh” 构成,知道前者个数和后面有几个字符 'h’即可
………

即 “nun” 由 "nu 构成,知道前者个数和后面有几个字符 'n’即可
即 “nu” 由 “n” 构成,知道前者个数和后面有几个字符 'u’即可
即 “n” 由 “” 构成,知道前者个数和后面有几个字符 'n’即可

会发现最后的结果都跟上一个操作有关系,所以就可以写出dp方程找到答案
动态方程 a[i][j] = a[i-1][j-1] + a[i][j-1] (意思为,第 i 个前缀的数为第 i-1 个前缀在当前项 j 位的前一位上(即j-1 位上的值,表示上一个前缀的个数,在第7位时,刚好串S的第j个和串Q相同) + 此时第 i 个前缀上一项 j-1 的个数(即之前已经出现过该前缀和的次数))

0 1 2 3 4 5 6 7 8 9 10 11 (行表示匹配第几个前缀,列表示匹配到字符串 Q的第几个字符)
_ n u n h e h h e h a a
0 0 0 0 0 0 0 0 0 0 0 0 不计算第一行和第一列,因为写前缀和会和上一行和上一列有关
1 1 1 2 2 2 2 2 2 2 2 2 -----------前缀和 “n” 的个数
2 0 1 1 1 1 1 1 1 1 1 1 -----------前缀和 “nu” 的个数
3 0 0 1 1 1 1 1 1 1 1 1 -----------前缀和 “nun” 的个数
4 0 0 0 1 1 2 3 3 4 4 4 -----------前缀和 “nunh” 的个数
5 0 0 0 0 1 1 1 2 2 2 2 -----------前缀和 “nunhe” 的个数
6 0 0 0 0 0 1 2 2 3 3 3 -----------前缀和 “nunheh” 的个数
7 0 0 0 0 0 0 1 1 2 2 2 -----------前缀和 “nunhehh” 的个数
8 0 0 0 0 0 0 0 1 1 1 1 -----------前缀和 “nunhehhe” 的个数
9 0 0 0 0 0 0 0 0 1 1 1 前缀和 “nunhehheh” 的个数

对于最后一行,所有的前缀和求个数的时候,dp方程不需要考虑上一项 j-1 的值,只需要考虑是否上一个前缀存在,若存在,并且Q[j] == S[i],表示满足这一项的前缀和字符的要求,加和即可

上面计算完前缀问题,就考虑后缀个数、以及前后缀匹配的问题

对于字符串 Q 的后半部分 “aaa”
…a a a
…3 2 1 ---------(表示从后往前来求,第几位的 a 的个数)

用数组 A 来存
最后求得匹配的个数就是
dp[i][j] * (2A[i]- 1)

因为对于 i 项,后面 a 的个数的拿去任意排列为 2n-1

最后就是不要忘记开 long long 使用快速幂,以及取模

代码如下在这里插入代码片

#include<stdio.h>
#include<string.h>
typedef long long ll;
ll M = 998244353;
char s[100010];
int a[11][100010] = { 0 };
int A[100010];
char Q[] = { "qnunhehheh" };	//第一项随便存 

ll ksm(ll a, ll p) {
	ll res = 1;
	while (p) {
		if (p & 1) res = res * a % M;
		a = a * a % M;
		p >>= 1;
	}
	return res;
}
int main()
{
	int t;
	scanf("%d", &t);
	while (t--) {
		memset(a, 0, sizeof(a));	//每次都要初始化 
		memset(A, 0, sizeof(A));
		getchar();		//吃回车 
		scanf("%s", &s[1]);
		int n = strlen(&s[1]);
		for (int j = 1; j <= n; j++)	// i = 1 拿出来特判 
			if (s[j] == Q[1]) a[1][j] = (a[1][j - 1] + 1)%M;
			else a[1][j] = a[1][j - 1];
		for (int i = 2; i <= 8; i++) {
			for (int j = 1; j <= n; j++) {
				if (s[j] == Q[i]) a[i][j] = (a[i - 1][j - 1] + a[i][j - 1])%M;
				else a[i][j] = a[i][j - 1];
			}
		}
		for(int i = 1;i <= n;i++){
			if(s[i] == Q[9]) a[9][i] = a[8][i-1];
		}

		for (int i = n; i >= 1; i--) {
			if (s[i] == 'a') A[i] = A[i + 1] + 1;
			else A[i] = A[i + 1];
		}
		ll sum = 0;
		for (int i = 1; i <= n; i++) {
			ll a_s = ksm(2, A[i]) - 1;
			sum = (sum + a[9][i] * a_s) % M;
		}
		printf("%lld\n", sum);
	}
	return 0;
}

//nunhehhhehahaahahahahahahaahaahahahahha



这种字符匹配的dp,还有类型:最长公共子序列、KMP匹配(子串匹配)等

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值