洛谷 P2679 子串

题意

从字符串\(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;
}

猜你喜欢

转载自www.cnblogs.com/ehznehc/p/10897159.html