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

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 只依赖 i-1,因此用滚动数组(两套状态 curpre)。最终答案为 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;
}
相关推荐
June`5 小时前
redis项目之命令解析器
数据库·c++·redis
rGzywSmDg5 小时前
如何在Dev-C++中设置TDM-GCC为默认编译器
开发语言·c++
csdn_aspnet6 小时前
C++ Lomuto分区算法(Lomuto Partition Algorithm)
开发语言·c++·算法
会周易的程序员7 小时前
aiDgeScanner:工业设备扫描与管理的一体化利器——深度解析上位机与扫描端的无缝协作
c++·物联网·typescript·electron·vue·iot·aiot
BirdenT7 小时前
20260518紫题训练
c++·算法
lingzhilab9 小时前
零知派ESP32——BLE Mesh蓝牙组网智能灯控系统(PIR感应+W2812三档调色)
c++·mfc
计算机安禾9 小时前
【c++面向对象编程】第29篇:定位new(placement new):在指定内存上构造对象
开发语言·c++·算法
计算机安禾9 小时前
【c++面向对象编程】第27篇:空类的大小为什么是1?——C++对象标识的秘密
开发语言·c++·算法