【洛谷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}}\) ;
-
若选择,则分两种情况讨论
-
\(\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}}\) ;
-
\(\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); //优化后输出也要改呀~
}
浙公网安备 33010602011771号