这题目第一眼扫过去就是DP,状态的表示熟练的人也不能想出来用$f[i,j,k]$表示当$A$匹配到前$i$位,$B$字符串匹配到$j$位,将$A$序列分成$k$段时候所能得到的数量。
总共有两种情况:
一:$i$这一位不与$j$匹配,即用前$i-1$去匹配$B$的前$j$位;二:$i$这一位与$i$前几位能与$B$的$j$这一位与其前几位一起匹配,作为一个单独的段(也就是有相同的后缀)。那么就有$f[i,j,k]=f[i-1][j][k]$(不匹配)$+\sum_{L}f[i-L-1][j-L-1][k-1]$(满足$A[i-L..i]=B[j-L..j]$,也就是所说的一起匹配)。
那么注意好初始化不难写出一个朴素的代码:
for(int i=0;i<=n;++i) f[i][0][0]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=m && j<=i;j++){ for(int p=1;p<=k && p<=j;p++){ f[i][j][p]+=f[i-1][j][p]; for(int l=0;l<i && l<j && A[i-l]==B[j-l];l++) f[i][j][p]+=f[i-l-1][j-l-1][p-1]; } } }
可惜,这样写会挂掉,时间和空间上都是。先考虑时间上的优化,可以优化的地方只剩下循环找一起匹配的段哪里了。我们想如果$A[i-L..i]=B[j-L..j]$的话,那么$f[i-1,j-1,k]$是不是保存了求$\sum_{L}f[i-L-1][j-L-1][k-1]$的那一段呢?(因为就相当于$A[i-1..i]$与$B[j-1..j]$一起匹配进去然后算)。
但是如果这样简单的改一下代码是不行的:
for(int i=0;i<=n;++i) f[i][0][0]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=m && j<=i;j++){ for(int p=1;p<=k;p++){ f[i][j][p]+=f[i-1][j][p]; if(A[i]==B[j]){ f[i][j][p]+=f[i-1][j-1][p-1]; if(A[i-1]==B[j-1]) f[i][j][p]+=f[i-1][j-1][p]; } } } }
因为这里的$f$数组保存的信息不一定能构成连续,它也会保存前面不能连续匹配的数据,比如这组数据:
6 3 2
aabaab
aab
按照上面代码跑出来的$f[4,1,1]=3$这本身没错,但是看$f[5,2,2]=f[4,2,2]+f[4,1,0]+f[4,1,1]=1+0+3=4$,显然有误。仔细思考就会发现我们可以新建一个数组$g$,当$A[i]!=B[j]$的时候把$g$置为$0$,当$A[i]=B[j]$的时候令$g[i][j][k]=g[i-1][j-1][k]$(和前面连续的一同构成这第k段)$+f[i-1][j-1][k-1]$(单独构成第k段),而$f[i][j][k]=g[i][j][k]+f[i-1][j][k]$就行了。
然后就是空间的优化了,像背包一样把第一维省略掉,第二位倒序循环就ok了。
最后附上代码:
#include<bits/stdc++.h> using namespace std; const int N=1005,M=205,mod=1000000007; typedef long long ll; char A[N],B[N]; ll f[M][M],g[M][M]; int n,m,k; int main(){ scanf("%d%d%d",&n,&m,&k); scanf("%s",A+1); scanf("%s",B+1); f[0][0]=1; for (int i=1;i<=n;i++){ for(int j=min(m,i);j>=1;j--) { for(int p=1;p<=k && p<=j;p++) { if(A[i]==B[j]) g[j][p]=(g[j-1][p]+f[j-1][p-1])%mod; else g[j][p]=0; f[j][p]=(g[j][p]+f[j][p])%mod; } } } printf("%lld",f[m][k]%mod); return 0; }