cen114514

导航

【洛谷P2679 [NOIP 2015 提高组] 子串】题目分析

【洛谷题目链接:P2679

依旧绿题~

本人做法\(\mathbf{\color{red}dp+枚举}\),方法有点小绕(主要是为了避免MLE),不过代码实现还是比较轻松 ( &w& )

话不多说直接看题 =>

\(\mathbf{\Large{题目}}\)

题目背景

NOIP2015 Day2T2

题目描述

有两个仅包含小写英文字母的字符串 \(A\)\(B\)

现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 \(B\) 相等?

注意:子串取出的位置不同也认为是不同的方案。

输入格式

第一行是三个正整数 \(n,m,k\),分别表示字符串 \(A\) 的长度,字符串 \(B\) 的长度,以及问题描述中所提到的 \(k\),每两个整数之间用一个空格隔开。

第二行包含一个长度为 \(n\) 的字符串,表示字符串 \(A\)

第三行包含一个长度为 \(m\) 的字符串,表示字符串 \(B\)

输出格式

一个整数,表示所求方案数。

由于答案可能很大,所以这里要求输出答案对 \(1000000007\) 取模的结果。

输入输出样例 #1

输入 #1

6 3 1 
aabaab 
aab

输出 #1

2

输入输出样例 #2

输入 #2

6 3 2 
aabaab 
aab

输出 #2

7

输入输出样例 #3

输入 #3

6 3 3 
aabaab 
aab

输出 #3

7

说明/提示

样例解释

所有合法方案如下:(加下划线的部分表示取出的字串)

样例 1:\(\underline{aab}\,aab,aab\,\underline{aab}\)
样例 2:\(\underline{a}\,\underline{ab}\,aab,\underline{a}\,aba\,\underline{ab},a\,\underline{a}\,ba\,\underline{ab},aab\,\underline{a}\,\underline{ab},\underline{aa}\,\underline{b}\,aab,\underline{aa}\,baa\,\underline{b},aab\,\underline{aa}\,\underline{b}\)
样例 3:\(\underline{a}\,\underline{a}\,\underline{b}\,aab,\underline{a}\,\underline{a}\,baa\,\underline{b},\underline{a}\,ab\,\underline{a}\,a\,\underline{b},\underline{a}\,aba\,\underline{a}\,\underline{b},a\,\underline{a}\,b\,\underline{a}\,a\,\underline{b},a\,\underline{a}\,ba\,\underline{a}\,\underline{b},aab\,\underline{a}\,\underline{a}\,\underline{b}\)

数据范围

对于第 1 组数据:\(1≤n≤500,1≤m≤50,k=1\);
对于第 2 组至第 3 组数据:\(1≤n≤500,1≤m≤50,k=2\);
对于第 4 组至第 5 组数据:\(1≤n≤500,1≤m≤50,k=m\);
对于第 1 组至第 7 组数据:\(1≤n≤500,1≤m≤50,1≤k≤m\);
对于第 1 组至第 9 组数据:\(1≤n≤1000,1≤m≤100,1≤k≤m\);
对于所有 10 组数据:\(1≤n≤1000,1≤m≤200,1≤k≤m\)

\(\mathbf{\Large{思路}}\)

\(\mathbb{ 开头说过嘛,这道题用dp+枚举 }\)

dp状态:


既然要用dp,那先确定一下状态所包含的元素

  • 首先既然要保证匹配,那么推到\(\mathbf{\color{blue}A串字符和B串字符的位置}\)肯定是需要的;

  • 要确保最终划分的字符串数为题目给出的k,还要记录\(\mathbf{\color{blue}此位置划分了几个字符串}\)

  • 为了方便递推,还要在每个点标记一个状态 0/1,表示A中的\(\mathbf{\color{blue}此字符是否被选入}\)

我们把以上4个元素,分别定义为\(i, j, p, sta\);

\(dp[i][j][p][sta]: \mathbf{A中的1 - i的位置使用 p 个子串匹配B中的1 - j的位置且第 i 个位置状态为 sta ( 0:不选 / 1:选 )的方案数}\)

递推方程式:


  • 不选此位字符\((sta = 0)\)时,\(\Large{dp_{i,j,p,0} = dp_{i-1,j,p,0} + dp_{i-1,j,p,1}}\) ;

  • 若选择,则分两种情况讨论

    1. \(\large{a_{i} = b_{j}}\) 时,\(\Large{dp_{i,j,p,1} = dp_{i-1,j-1,p,1} + dp_{i-1,j-1,p-1,1} + dp_{i-1,j-1,p-1,0}}\) ;

    2. \(\large{a_{i} \ne b_{j}}\) 时,\(\Large{dp_{i,j,p,1} = 0}\) ( 选不了的嘛 ) ;

循环枚举每一种状态

最后输出 \(\Large{dp_{n,m,p,0} + dp_{n,m,p,1}}\) 就好了

优化空间复杂度:


以上代码写完后,

时间复杂度为\(O(nmk)\),完全可以接受;

空间复杂度为\(O(2nmk)\),也就是\(2\times{1000}\times{200}\times{200}=8\times{10^{7}}\),算是在MLE的边缘疯狂试探吧 (hhh...

直接这么交,运气好点可能就AC了,庆幸洛谷给的数据范围小

这时就有人会说了,\(\large{\mathit{呀嘚呀嘚那要是NOIP当时给的数据大一点,你不就炸了吗(戳)}}\)

别急,这不是还有优化嘛

仔细观察我们的转移方程,你会发现一个非常神奇的事:每次转移,竟然只用到了当前位和前一位耶!すごいですね!

咳咳,所以我们就可以把第一维由 n 改为 2,这不就直接滚掉了

现在的空间复杂度就变成了\(O(4mk)\),OK啦~

!!!别!忘!了!取!余!!! 1e9+7!!!


最后附上完整代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7, maxn = 1e3 + 5, maxm = 2e2 + 5;
int n, m, k, dp[2][maxm][maxm][2];
char A[maxn], B[maxm];
void scf(char a[], int len) {
	string s;
	cin>> s;
	for(int i=1; i<=len; i++)
		a[i] = s[i-1] - '0'; //正序将string中的字符正序存入数组
}
int main() {
	scanf("%d%d%d", &n, &m, &k);
	scf(A, n); scf(B, m);
	memset(dp, 0, sizeof(dp));
	dp[0][0][0][0] = 1;
	dp[1][0][0][0] = 1;  //记得初始化啊qwq要不然后面都是0
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=m; j++) {
			for(int p=1; p<=k; p++) {
				if(A[i] == B[j]) {
					dp[i%2][j][p][1] = ((dp[(i-1)%2][j-1][p][1] + dp[(i-1)%2][j-1][p-1][0]) % mod + dp[(i-1)%2][j-1][p-1][1]) % mod;
					dp[i%2][j][p][0] = (dp[(i-1)%2][j][p][1] + dp[(i-1)%2][j][p][0]) % mod;
				}
				else {
					dp[i%2][j][p][0] = (dp[(i-1)%2][j][p][1] + dp[(i-1)%2][j][p][0]) % mod;
					dp[i%2][j][p][1] = 0;
				}//分情况讨论
			}
		}
	}
	printf("%d", (dp[n%2][m][k][0] + dp[n%2][m][k][1]) % mod); //优化后输出也要改呀~
}

完结撒花(๑>؂<๑)

posted on 2025-08-27 22:58  CHNcen  阅读(13)  评论(0)    收藏  举报