题意
从字符串(A)中取出(k)个互不重叠的非空子串,顺次连接起来得到一个新的字符串B,求方案总数。注意:子串取出的位置不同也认为是不同的方案。答案对(10^9+7)取模。
记(A)长度为(m),(B)长度为(n),所有数据满足
(1leq nleq 1000,1leq mleq 200,1leq kleq m)
思路
后缀自动机之类的玄学做法,即使可以过,也不适合蒟蒻在(NOIP)赛场上拿出来。这个数据范围也不适合(不记忆化的)搜索。很像(DP),但是要怎样(DP)呢?
按照(DP)的常规思路,先给(m),(n),(k)分别设置一个维度,记为
(f[i][j][p])
表示转移到(A[i])和(B[j]),使用了(p)个子串。一种思路是枚举(A)数组的([1,i])段再匹配。但是这不太好理解,可以人为地再增加一维布尔变量,相当于把一个状态分为两部分,其意义是(A[i])是否对应(B[i])。则有
$f[i][j][p][q],qin {0,1} $
状态转移
显然,决定转移方程的条件是(A[i]==B[j])。
如果(q=0),无论(A[i])与(B[j])相等与否,都直接照搬(A[i-1])、(B[j])的转移结果即可
(f[i][j][p][0]=f[i-1][j][p][0]+f[i-1][j][p][1])
如果(q=1),则分类讨论。
- 当(A[i]==B[j])时,答案是(A[i-1])、(B[j-1])的方案数。这也分为两种,一种是把(A[i]),(B[j])作为单独的子串,此时(p)要减一;一种是把子串(A[i]),(B[j])与(A[i-1]),(B[j-1])相连,此时(p)保持不变,(q)只能是(1)。
- 当(A[i]!=B[j])时,无法匹配,答案为(0)。
综上所述,得
(f[i][j][p][0]=f[i-1][j][p][0]+f[i-1][j][p][1])
(f[i][j][p][1]=egin{cases}{A[i]==B[j]quad f[i-1][j-1][p-1][1]+f[i-1][j-1][p-1][0]+f[i-1][j-1][p][1]}\{A[i]!=B[j]quad 0}end{cases})
还有一个问题,初值怎么设?
可以在第一层循环内加判断。如果(A[i])与(B[1])相同,则
(f[i][1][1][1]=1,f[i][1][1][0]=count,count++)
优化
空间大约是
(1000*200*200*2=8 imes 10^7)
这谁顶得住啊。观察发现,每次状态转移只和上一次结果有关。可以使用滚动数组优化。
代码
#include<iostream>
#include<cstring>
using namespace std;
int f[2][205][205][2],n,m,k,cnt,i=1;
string a,b;
const int mod=1000000007;
int main()
{
cin>>n>>m>>k;
cin>>a>>b;
a=' '+a,b=' '+b;
for(int temp=1;temp<=n;temp++,i^=1)
{
f[i][1][1][0]=cnt;
if(a[temp]==b[1]) f[i][1][1][1]=1,cnt++;
else f[i][1][1][1]=0;
for(int j=2;j<=m;j++)
for(int p=1;p<=k;p++)
{
f[i][j][p][0]=(f[i^1][j][p][1]+f[i^1][j][p][0])%mod;
f[i][j][p][1]=(a[temp]==b[j])*((f[i^1][j-1][p-1][1]+f[i^1][j-1][p-1][0])%mod+f[i^1][j-1][p][1])%mod;
}
}
cout<<(f[n&1][m][k][1]+f[n&1][m][k][0])%mod;
return 0;
}