csp信奥赛C++高频考点专项训练之字符串 --【字符串综合】:[NOIP 2015 提高组] 子串

题目描述
有两个仅包含小写英文字母的字符串 A A A 和 B B B。
现在要从字符串 A A A 中取出 k k k 个互不重叠的非空子串,然后把这 k k k 个子串按照其在字符串 A A A 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 B B B 相等?
注意:子串取出的位置不同也认为是不同的方案。
输入格式
第一行是三个正整数 n , m , k n,m,k n,m,k,分别表示字符串 A A A 的长度,字符串 B B B 的长度,以及问题描述中所提到的 k k k,每两个整数之间用一个空格隔开。
第二行包含一个长度为 n n n 的字符串,表示字符串 A A A。
第三行包含一个长度为 m m m 的字符串,表示字符串 B B B。
输出格式
一个整数,表示所求方案数。
由于答案可能很大,所以这里要求输出答案对 1000000007 ( 10 9 + 7 ) 1000000007(10^9 + 7) 1000000007(109+7) 取模的结果。
输入输出样例 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 1 1 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = 1 1\le n\le 500,1\le m\le 50,k=1 1≤n≤500,1≤m≤50,k=1;
对于第 2 2 2 组至第 3 3 3 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = 2 1\le n\le 500,1\le m\le 50,k=2 1≤n≤500,1≤m≤50,k=2;
对于第 4 4 4 组至第 5 5 5 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = m 1\le n\le 500,1\le m\le 50,k=m 1≤n≤500,1≤m≤50,k=m;
对于第 1 1 1 组至第 7 7 7 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , 1 ≤ k ≤ m 1\le n\le 500,1\le m\le 50,1\le k\le m 1≤n≤500,1≤m≤50,1≤k≤m;
对于第 1 1 1 组至第 9 9 9 组数据: 1 ≤ n ≤ 1000 , 1 ≤ m ≤ 100 , 1 ≤ k ≤ m 1\le n\le 1000,1\le m\le 100,1\le k\le m 1≤n≤1000,1≤m≤100,1≤k≤m;
对于所有 10 10 10 组数据: 1 ≤ n ≤ 1000 , 1 ≤ m ≤ 200 , 1 ≤ k ≤ m 1\le n\le 1000,1\le m\le 200,1\le k\le m 1≤n≤1000,1≤m≤200,1≤k≤m。
思路分析
题目要求从字符串 A 中取出 k 个互不重叠的非空子串,按顺序拼接后等于 B,求方案数(位置不同算不同方案)。
数据范围:1 ≤ n ≤ 1000,1 ≤ m ≤ 200,1 ≤ k ≤ m,需要 O(nmk) 的 DP。
状态设计
设 dp[i][j][l][0/1] 表示考虑 A 的前 i 个字符,匹配了 B 的前 j 个字符,已经使用了 l 个子串,并且第 i 个字符 未选 (0) 或 已选 (1) 的方案数。
- 当第 i 个字符未选时,方案数直接继承前 i-1 个字符的所有方案(无论第 i-1 个选没选):
dp[i][j][l][0] = dp[i-1][j][l][0] + dp[i-1][j][l][1] - 当第 i 个字符被选时,必须满足
A[i] == B[j]且 j ≥ 1:- 若该字符作为一个新子串的开头,则前 i-1 个字符必须恰好匹配到 B 的前 j-1 个,且只用了 l-1 个子串,且第 i-1 个字符未选(避免子串重叠):
dp[i][j][l][1] += dp[i-1][j-1][l-1][0] - 若该字符延续上一个子串,则前 i-1 个字符匹配到 B 的前 j-1 个,用了 l 个子串,且第 i-1 个字符被选:
dp[i][j][l][1] += dp[i-1][j-1][l][1]
- 若该字符作为一个新子串的开头,则前 i-1 个字符必须恰好匹配到 B 的前 j-1 个,且只用了 l-1 个子串,且第 i-1 个字符未选(避免子串重叠):
空间优化
第一维 i 只依赖 i-1,因此用滚动数组(两套状态 cur 和 pre)。最终答案为 dp[pre][m][k][0] + dp[pre][m][k][1](处理完所有 A 字符后的总方案数)。
复杂度
时间复杂度 O(nmk) ≈ 1000×200×200 = 4×10⁷,空间 O(mk) ≈ 4×10⁴。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n,m,k,dp[2][205][205][2]; // 滚动数组 dp[当前/上一][匹配长度][子串数][是否选]
string a,b; // 字符串 A 和 B
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k>>a>>b; // 读入长度和字符串
a=" "+a; b=" "+b;// 下标从1开始,方便匹配
dp[0][0][0][0]=1;// 初始状态:A前0个,匹配0,0子串,未选
int cur=1,pre=0;// cur当前层,pre上一层
for(int i=1;i<=n;++i){// 枚举A的每个字符
memset(dp[cur],0,sizeof(dp[cur])); // 清空当前层
for(int j=0;j<=m;++j){ // 枚举匹配长度
for(int l=0;l<=k;++l){// 枚举子串个数
// 不选当前字符
dp[cur][j][l][0]=(dp[pre][j][l][0]+dp[pre][j][l][1])%MOD;
// 选当前字符(必须匹配且至少1个子串)
if(j>0 && l>0 && a[i]==b[j]){
// 新开一个子串
dp[cur][j][l][1]=(dp[cur][j][l][1]+dp[pre][j-1][l-1][0])%MOD;
// 延续上一个子串
dp[cur][j][l][1]=(dp[cur][j][l][1]+dp[pre][j-1][l][1])%MOD;
}
}
}
swap(cur,pre);// 滚动:当前层变上一层
}
int ans=(dp[pre][m][k][0]+dp[pre][m][k][1])%MOD;// 最终答案
cout<<ans<<'\n';
return 0;
}
功能分析
-
输入处理 :读入
n, m, k及字符串 A、B,通过前置空格使下标从 1 开始,方便 DP 转移时直接比较a[i]与b[j]。 -
状态转移:
- 不选第 i 个字符:方案数为上一轮所有可能方案的和。
- 选第 i 个字符:仅在字符相等且子串数至少为 1 时发生,分新开子串和延续子串两种情况累加。
-
空间优化 :使用二维滚动数组(
dp[2][205][205][2])只保留当前层和上一层,避免高内存开销。 -
时间复杂度:O(n × m × k) ≈ 4×10⁷,在 1 秒左右可运行完成。
【完整系列请查看专栏】:
信奥赛C++普及组CSP-J一等奖通关刷题题单及题解:
https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
【秘籍汇总】(完整csp信奥赛C++学习资料):
1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转

2、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

https://edu.csdn.net/course/detail/41081 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html 点击跳转
4、csp信奥赛冲刺一等奖有效刷题题解:
信奥赛C++普及组CSP-J一等奖通关刷题题单及题解:
https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
信奥赛C++提高组csp-j初赛&复赛真题题解(持续更新): https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13125089.html 点击跳转
5、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}