题意
从字符串\(A\)中取出\(k\)个互不重叠的非空子串,顺次连接起来得到一个新的字符串B,求方案总数。注意:子串取出的位置不同也认为是不同的方案。答案对\(10^9+7\)取模。
记\(A\)长度为\(m\),\(B\)长度为\(n\),所有数据满足
\(1\leq n\leq 1000,1\leq m\leq 200,1\leq k\leq 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],q\in {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]=\begin{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\times 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;
}