题解 P2679 【子串】

· · 题解

蒟蒻表示研究DP方程很久的说

尽管各位大佬都觉得此题DP方程很简单

但是可能也会有像我这样的蒟蒻看的不是很懂

于是在蒟蒻研究了很久之后,终于AC了

之后蒟蒻将会从动规的三要素讲起

关于阶段

由题可知,阶段应如此划分(蒟蒻认为

A串的第i位 匹配到了B串第j位 使用了p个子串

(应该可以理解吧)

至于为什么?咱们看看题目

从A串中取出k个互不重叠的非空子串,把这k个子串按照出现的顺序依次连接起来得到一个新的字符串,并使得这个新串与B串相等

嗯嗯嗯,我相信各位都懂了吧

关于状态

状态一: A串第i位字符

状态二: B串第j位字符

状态三: 当前使用的子串数p(注意p<=k)

以及 状态四:a[i]是否能匹配得上b[j]

还有状态五,但是这个稍作悬念,下一栏再进一步分析

关于决策

但是由于状态四的不确定性,得分两种情况

1.a[i]=b[j]时

决策一:使用该字符匹配

决策二:不使用该字符匹配

2.a[i]!=b[j]时

只能不使用该字符匹配

由此可见,状态五便华丽丽地登场了

那就是 是否会使用该字符

我们可以用0表示不使用,1表示使用

综上所述 得出动态转移方程

1.a[i]=b[j]时

决策一: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]

代码里也有解释,不过不是很详尽

对于上述的数组解释如下

f[i][j][p][1]:A串前i个使用了k个子串匹配到B串前j个,并且使用了当前a[i]的种数 

f[i-1][j-1][p][1]:将当前字符纳入前一个子串,并使用前一个字符的种数 

f[i-1][j-1][p-1][0]:将当前字符纳入新子串,但不使用前一个字符的种数 

f[i-1][j-1][p-1][1]:将当前字符纳入新子串,并使用前一个字符的种数

对于上述方程的解释:

将该字符纳入前一个子串算一种情况(因为纳入前一个子串,故肯定是使用)

将该字符单独看成一个新子串算一种,而看成一个新子串,又分两种:第一种是不使用前一个字符,第二种是不使用前一个字符。(因为是新子串,所以前一个字符与当前字符是独立的,所以有两种)

决策二:f[i][j][p][0]=f[i-1][j][p][1]+f[i-1][j][p][0]

未使用该字符,故B子串匹配数仍为j,子串仍为k.

未使用该字符的种数即前一个字符使用的种数+前一个字符未使用的种数(继承上一阶段的)

2.a[i]!=b[j]时

f[i][j][p][1]=0

不能使用该字符,所以为0

f[i][j][p][0]=f[i-1][j][p][1]+f[i-1][j][p][0];

因为不能使用该字符,所以便继承上一阶段的值

提醒

数组 1000 * 200 * 200 * 2肯定会爆,故滚动第一维(因为第一维只有i和i-1)

最后答案应该为f[n][m][k][1]+f[n][m][k][0]相加

(最后一个字符使用和不使用是两种情况嘛,最终答案应该是他们之和)

上代码

于是,各位最爱的代码上线了

#include<iostream>
#include<cstdio>
using namespace std;
int md=1000000007;
int n,m,k,f[2][222][222][2];
char a[1001],b[201];
int main()
{
    int i,j,p;
    scanf("%d%d%d",&n,&m,&k);
    for(i=1;i<=n;i++)
        cin>>a[i];
    for(i=1;i<=m;i++)
        cin>>b[i];
    f[0][0][0][0]=1;
    f[1][0][0][0]=1;//初始化,否则之后都得是0
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            for(p=1;p<=k;p++)
            {
                if(a[i]==b[j])
                {
                    f[i%2][j][p][1]=(f[(i-1)%2][j-1][p][1]+f[(i-1)%2][j-1][p-1][0])%md+f[(i-1)%2][j-1][p-1][1]%md;
                    //f[i-1][j-1][p][1]:将当前字符纳入前一个子串,并使用前一个字符的种数 
                    //f[i-1][j-1][p-1][0/1]:将当前字符纳入新子串,但不使用(使用)前一个字符的种数 
                    f[i%2][j][p][1]%=md;
                    f[i%2][j][p][0]=f[(i-1)%2][j][p][1]+f[(i-1)%2][j][p][0];
                    //未使用该字符的种数即前一个字符使用的种数+前一个字符未使用的种数 
                    f[i%2][j][p][0]%=md;
                }
                else
                {
                    f[i%2][j][p][0]=f[(i-1)%2][j][p][1]+f[(i-1)%2][j][p][0];
                    f[i%2][j][p][0]%=md;
                    f[i%2][j][p][1]=0;//不能使用该字符
                 } 
            }
    printf("%d",(f[n%2][m][k][1]+f[n%2][m][k][0])%md);
    return 0;
}