题解 P2679 【子串】

frankchenfu

2018-02-22 21:38:35

Solution

看到各位DP数组都只开两三维的我很害怕啊,我来讲一讲如何很暴力的做这道题。 ------------ ### 状态 首先我们考虑设计状态。我们发现,为了保证无后效性的一位一位往后推,我们需要记录当前推到$a$串的哪一个位置了;接着还有记录匹配了$b$串的那几个字符。因为是按照原串顺序,所以相当于是即匹配$b$的前几个字符。有这些还不够,我们还要记录划分了几个子串。最后,为了便于转移,我们还要标记一维`0/1`状态,表示$a$串中的第$i$个字符是否选入。 这样,我们就设计好了状态。我们记$f_{i,j,p,v}$表示到$a$串的第$i$个位置为止使用$p$个子串匹配$b$串前$j$位字符且第$i$个位置选或不选($v$)的方案数。 ------------ ### 转移 设计好状态,不会转移怎么行。我们分情况考虑。 1. 当$a_i=b_j$时: 1. $f_{i,j,p,0}$:由于这位不选,所以就是前面一位选和不选方案数之和,即$f_{i,j,p,0}=f_{i-1,j,p,0}+f_{i-1,j,p,1}$。 2. 容易得到$f_{i,j,p,1}=f_{i-1,j-1,p,1}+f_{i-1,j-1,p-1,0}+f_{i-1,j-1,p-1,1}$. 2. 当$a_i\ne b_j$时: 1. 不选情况同上,即$f_{i,j,p,0}=f_{i-1,j,p,0}+f_{i-1,j,p,1}$. 2. 由于选不了,自然就是$0$,即$f_{i,j,p,1}=0$. ------------ ### 优化空间 如果你读完状态设计之后又稍微思考就会发现,空间可能较大。空间不够怎么办?在luogu还好说,如果真的在NOIP,应该是不敢开$1000\times200\times200\times2=8\times10^7$的数组吧。所以我们观察转移方程,发现每次转移只用到了前一位!于是我们把第一维很愉快地滚掉了。这样,空间复杂度就保证是$O(mk)$了。那么时间呢?时间是$O(n\cdot mk)$,但是时间不像空间,这个复杂度是可以接受的。于是,完整算法就结束了。 ------------ `Cpp`代码: ```cpp #include<cstdio> #include<cstring> const int MAXN=1010; const int MAXM=210; const int MOD=(int)(1e9)+7; int f[2][MAXM][MAXM][2]; char a[MAXN],b[MAXM]; int n,m,k;bool val=1; void dp(){ f[0][0][0][0]=f[1][0][0][0]=1; for(int i=1;i<=n;i++,val^=1) for(int j=1;j<=m;j++) for(int p=1;p<=k;p++){ if(a[i]==b[j]){ f[val][j][p][0]=(f[val^1][j][p][0]+f[val^1][j][p][1])%MOD; f[val][j][p][1]=(f[val^1][j-1][p][1]+\ (f[val^1][j-1][p-1][0]+f[val^1][j-1][p-1][1])%MOD)%MOD; } else{ f[val][j][p][0]=(f[val^1][j][p][0]+f[val^1][j][p][1])%MOD; f[val][j][p][1]=0; } } } int main(){ scanf("%d%d%d",&n,&m,&k); scanf("%s%s",a+1,b+1); dp(); printf("%d\n",(f[n&1][m][k][0]+f[n&1][m][k][1])%MOD); return 0; } ```