2015D2T2 子串
problem
给定两个小写英文字母串(A,B),从(A)中取出(k)个子串按照先后顺序连接得到新的字符串。求可以使新串和(B)相等的方案数。答案对(10^9+7)取模。
solution
(f(i,j,k))表示A串前i个字符里,使用了k个子串,来匹配了B串前j个字符。
可列出方程(f(i,j,k)=f(i-1,j-1,k-1)+f(i-1,j-1,k)),即为A串前i个字符里,使用了k个子串,匹配了B串前j个字符的方案数,等价于单独使用第i-1个字符作为一个子串的方案数和不单独使用的方案数
但是我们要考虑到使用和不使用,这个就涉及到0/1的这一维。所以我们添加0/1维,来表示使用了当前字符和没使用当前的字符。
转移方程
[f(i,j,k,0)=f(i-1,j,k,0)+f(i-1,j,k,1)
]
如果第i位不使用
第i-1位未使用时,前i-1位匹配前j位使用k个子串
第i-1位使用时,前i-1位匹配前j位使用k个子串
[[a_i=b_j]f(i,j,k,1)=f(i-1,j-1,k-1,0)+f(i-1,j-1,k,1)+f(i-1,k-1,k-1,1)
]
如果第i位使用,即a[i]==b[j]
- 第i-1位未使用而第i位作为一个新子串
- 第i位和前面连在一起,不单独使用
- 第i-1位使用了,但第i位仍作为一个新子串。
边界条件
对于A子串前i位,匹配B串第1个字符,那么只可能使用1个字符串,这种情况如果第i位不进行匹配,那么方案数就是之前所有能与第1位匹配的字符,所以
[f(i,1,1,0)=sumlimits_{i=1}^{n-1}[a_i=b_1]
]
同上,但如果B串第一位和A串第一位匹配了话,那么
[f[i][1][1][1]=1
]
是显然的。
总结
[f(i,j,k,0)=f(i-1,j,k,0)+f(i-1,j,k,1)
]
[[a_i=b_j]f(i,j,k,1)=f(i-1,j-1,k-1,0)+f(i-1,j-1,k,1)+f(i-1,k-1,k-1,1)
]
[f(i,1,1,0)=sumlimits_{i=1}^{n-1}[a_i=b_1]
]
[f[i][1][1][1]=1
]
维度优化
方程里只有i和i-1被来回调用,其余例如i-2,i-3在接下来的动态规划里是不需要的,可以把数组滚动起来。设置一个t1,t2变量分别是0,1,来回变换。最后交换的时候记得清零。
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
long long read(){
int a=0,op=1;char c=getchar();
while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
return a*op;
}
const int maxn=1005;
const int mod=1e9+7;
char a[maxn],b[maxn];
long long n,m,k;
long long dp[2][201][201][2],s,t1,t2=1;
int main(){
n=read(),m=read(),k=read();
scanf("%s%s",a+1,b+1);
for(int i=1;i<=n;i++){
swap(t1,t2);
dp[t1][1][1][0]=s;
if(a[i]==b[1]) dp[t1][1][1][1]=1,s++;
for(int j=2;j<=m;j++){
for(int u=1;u<=k;u++){
if(a[i]==b[j]) dp[t1][j][u][1]=((dp[t2][j-1][u-1][1]+dp[t2][j-1][u][1])%mod+dp[t2][j-1][u-1][0])%mod;
dp[t1][j][u][0]=(dp[t2][j][u][0]+dp[t2][j][u][1])%mod;
}
}
for(int j=1;j<=m;j++) for(int u=1;u<=k;u++) dp[t2][j][u][1]=dp[t2][j][u][0]=0;
}
printf("%lld",(dp[t1][m][k][1]+dp[t1][m][k][0])%mod);
return 0;
}