Noip2015 子串 【动态规划】

本文介绍了一种使用动态规划解决字符串匹配问题的方法,通过设计状态f(i,j,k)和s(i,j,k),实现了对两个字符串中特定子串匹配方案数的计算。文章详细解析了状态转移方程,并给出了两种实现代码:一种是直接实现,另一种是通过滚动数组优化空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题思维量是可以的=。=
学习了dalao们的状态设计,觉得自己还是差很多啊!
先说一个没有滚动数组的版本
对于A串的一个字符,只有选和不选两种可能,选是一种方案,不选也是一种方案

f(i,j,k)f(i,j,k) 为A串的前 i 项在分离出 第k个子串成功匹配到B串的前 j 项时 选择 A串第i项的方案数

还需要加上A串第i项不选择的方案
s(i,j,k)s(i,j,k) 为A串的前 i 项在分离出 第k个子串成功匹配到B串的前 j 项的方案总数

那么s(n,m,k)s(n,m,k) 就是最终答案
整个过程就是用A串的每一个字符都去试试匹配B串的前j个字符,一直到 j = m,这时B串已经被完全匹配,然后用下一个字符再去匹配,最后把A中的每一个字符都试一遍

关于s的转移:s(i,j,k)=s(i1,j,k)+f(i,j,k)s(i,j,k)=s(i−1,j,k)+f(i,j,k)
这个转移也可以用阶段的方式来理解,s就是上一阶段(A试用第i-1个字符)时的方案总数加上A试用第i个字符时的方案总数

现在关键是求出f的转移
A[i]==B[j]时,i字符是可选的,i字符可以和i-1字符组成一个子串,也可以自立门户,单独成为一个新串
1.和i-1组成一个子串,由f(i1,j1,k)f(i−1,j−1,k) 转移而来
2.不和i-1组成一个子串,由s(i1,j1,k1)s(i−1,j−1,k−1) 转移而来
关于第二个转移,首先自立门户的话,子串数量加1,而状态定义为选择了第i个字符并且现在是第k个子串,那么i的方案数一定是由k-1号子串转移而来的
A[i]!=B[j]时,第i号字符显然是不可选的,所以f(i,j,k)=0f(i,j,k)=0

总状态转移方程:

f(i,j,k)={f(i1,j1,k)+s(i1,j1,k1)0(A[i] == B[j]) (A[i] != B[j])f(i,j,k)={f(i−1,j−1,k)+s(i−1,j−1,k−1)(A[i] == B[j])0 (A[i] != B[j])

s(i,j,k)=s(i1,j,k)+f(i,j,k)s(i,j,k)=s(i−1,j,k)+f(i,j,k)

代码(未优化空间,70分做法)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#define moder 1000000007
using namespace std;
char a[1010],b[210];
int f[1010][210][210],s[1010][210][210],n,m,kk;
int main() {
    cin >> n >> m >> kk;
    cin >> a+1 >> b+1;
    s[0][0][0] = 1;
    for(int i=1; i<=n; i++) {
        s[i][0][0] = 1;
        for(int j=1; j<=m; j++) {
            for(int k=1; k<=min(kk,j); k++) {
                if(a[i] == b[j]) {
                    f[i][j][k] = (f[i-1][j-1][k] + s[i-1][j-1][k-1]) % moder;
                    s[i][j][k] = (s[i-1][j][k] + f[i][j][k]) % moder;
                } else {
                    s[i][j][k] = s[i-1][j][k]; 
                }
            }
        }
    }
    cout << s[n][m][kk];
    return 0;
}

现在就应该开始滚动数组啦
可以看出,状态之间的转移只是i-1到i的转移,所以可以压掉i维,并且对j和k进行逆序枚举(每个字符只能选一次)

因为他们的转移都是从一个更小阶段的状态来的,所以逆序枚举不影响转移

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#define moder 1000000007
using namespace std;
char a[1010],b[210];
int f[210][210],s[210][210],n,m,K;
int main() {
    scanf("%d %d %d",&n,&m,&K);
    scanf("%s %s",a+1, b+1);
    s[0][0] = 1;
    for(int i=1; i<=n; i++) {
        for(int j=m; j>=1; j--) {
            for(int k=min(K,j); k>=1; k--) {
                if(a[i] == b[j]) {
                    f[j][k] = (f[j-1][k] + s[j-1][k-1]) % moder;
                    s[j][k] = (s[j][k] + f[j][k]) % moder;
                } else {
                    f[j][k] = 0;//这是必要的,即使不更新i,这个数组也会保存着i-1的值
                }
            }
        }
    }
    printf("%d",s[m][K]);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值