P2679 子串

Posted xcg123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2679 子串相关的知识,希望对你有一定的参考价值。

原题链接  https://www.luogu.com.cn/problem/P2679

技术图片

 

技术图片

 

题目大意

给你两个字符串 A 和 B,问你有几种方案使得将 B 分成不重复 k 段后每段在 A 中依次出现; 

题解

一般这种字符串 dp,还是两个字符串瞎搞的这种,状态设置是有套路的,然而我就不知道;

例如我们要求两个序列的 LCS,我们可以这样设置状态:

dp [ i ][ j ]:第一个序列的 1~i 和第二个序列的 1~j 的 LCS 是多少;

转移的话我们只需从前往后扫,按照每次添加一个字符的方式进行转移;

这个题也可以这么设置状态,只是多了一个维度而已,正所谓 zhx 说过:多一个限制就多一个维度!

状态设置

dp [ i ][ j ][ k ]:将 B 串的 1~j 分成不重复的 k 段,使得这 k 段在 A 中的 1~i 中依次出现的方案数是多少;

转移的话我们需要考虑分段的问题;

如果当前的 A [ i ] == B [ j ],那么说这个字符可以与上一个字符连起来作为一个更长的段,也可以独自成一段;如果 A [ i ] != B [ j ],那么就不能匹配,需要继续枚举下去;

但是这样求得的答案是不对的,因为有可能在 A [ i ] 的后面的某个位置 A [ i‘ ] 有 A [ i ] == A [ i‘ ] == B [ j ],而如果我们统计上 A [ i‘ ] 与 B [ j ] 的答案时就要懂得舍弃当前的 A [ i ] 来让 i 继续往下枚举;

我们再在基础上加一个维度:

dp [ i ][ j ][ k ][ 0/1 ]: 将 B 串的 1~j 分成不重复的 k 段,使得这 k 段在 A 中的 1~i 中依次出现,第 i 个字符选不选的方案数是多少;

什么意思呢?举个例子说一下啦:

技术图片

 

我们发现 A [ i ] == B [ j ],根据上面的说法,我们现在有两种抉择:

1. 让这个 b 和前面的 a 共为一段;

2. 让这个 b 单独成一段;

但是我们也可以舍弃当前的 A [ i ] ,继续往下匹配:

技术图片

这样,我们才能找全所有的情况 。 

状态转移

分为两种情况来讨论:

①:A [ i ] == B [ j ]:

那么我们现在有三种抉择了:

<1> 让当前字符与前面的字符连成一段,前提条件就是我们不能舍弃 A [ i ] 和 A [ i-1 ]:

dp [ i ][ j ][ k ][ 1 ] += dp [ i-1 ][ j-1 ][ k ][ 1 ];  

<2> 让当前字符独自成段,前提条件就是我们不能舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:

dp [ i ][ j ][ k ][ 1 ] += dp [ i-1 ][ j-1 ][ k-1 ][ 0 ] + dp [ i-1 ][ j-1 ][ k-1 ][ 1 ];

<3> 我们不选当前的 A [ i ],给后面的留个机会,前提条件是舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:

dp [ i ][ j ][ k ][ 0 ] += dp [ i-1 ][ j ][ k ][ 1 ] + dp [ i-1 ][ j ][ k ][ 0 ];

②:A [ i ] != B [ j ]:

<1> 毫无疑问,我们被迫舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:

dp [ i ][ j ][ k ][ 0 ] += dp [ i-1 ][ j ][ k ][ 0 ] + dp [ i-1 ][ j ][ k ][ 1 ];

<2> 哼,我偏要选!Sorry啦,方案数为 0 qwq:

dp [ i ][ j ][ k ][ 1 ] = 0;

发现舍弃 A [ i ] 时的转移方程是一样的,所以我们可以合到一块去,那么代码是长这个亚子滴:

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            for(int k=1;k<=K;k++)
            {
                dp[i][j][k][0]+=(dp[i-1][j][k][0]+dp[i-1][j][k][1])%mod;        //舍弃A[i]时代码一样 
                if(A[i]==B[j])                      //若相等,可以与前面的连成一段,也可以独自成段 
                    dp[i][j][k][1]+=((dp[i-1][j-1][k][1]+dp[i-1][j-1][k-1][0])%mod+dp[i-1][j-1][k-1][1])%mod;
                else dp[i][j][k][1]=0;              //不相等偏要选?方案数为0 
            }
        }
    }

边界设置

也就是刚开始 B 什么都没有的情况,那么对于任意的 i(i∈n),都有 dp [ i ][ 0 ][ 0 ][ 0 ] =1;

    for(int i=0;i<=n;i++) dp[i][0][0][0]=1;

 

但是会 MLE,O(nm2)直接 boom!

考虑到每次转移只与 A 串的前一个字符有关,所以我们可以滚动数组,只需开两个空间就够了,分别存当前状态和上一个状态;

转移的时候不能写 += 了,而是要直接赋值,不然你懂得qwq;

还需要用到位运算的小技巧,我们可以用 ^ 来实现 0~1 的转化,也就是说如果 i 是当前状态,i^1 就是上一个状态;

Code:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<0||ch>9)
    {
        if(ch==-) x=-x;
        ch=getchar();
    }
    while(ch>=0&&ch<=9)
    {
        a=(a<<1)+(a<<3)+(ch-0);
        ch=getchar();
    }
    return a*x;
}
const int mod=1e9+7; 
const int N=1005;
char A[N],B[N];
int n,m,K;
long long dp[2][205][205][2];        
int main()
{
    n=read();m=read();K=read();
    scanf("%s",A+1);scanf("%s",B+1);
    dp[0][0][0][0]=dp[1][0][0][0]=1;       //边界只需更新这两个就好了 
    for(int l=1;l<=n;l++)
    { 
        int i=l%2;                         //当前状态 
        for(int j=1;j<=m;j++)
        {
            for(int k=1;k<=K;k++)
            {
                dp[i][j][k][0]=(dp[i^1][j][k][0]+dp[i^1][j][k][1])%mod;     //舍弃A[i]时代码一样 
                if(A[l]==B[j])             //注意这里不是A[i]!!! 
                    dp[i][j][k][1]=((dp[i^1][j-1][k][1]+dp[i^1][j-1][k-1][0])%mod+dp[i^1][j-1][k-1][1])%mod;//若相等,可以与前面的连成一段,也可以独自成段       
                else dp[i][j][k][1]=0;     //不相等偏要选?方案数为0 
            }
        }
    }
    printf("%lld
",(dp[n%2][m][K][0]+dp[n%2][m][K][1])%mod);      //这里也别忘了改 
    return 0;
}

以上是关于P2679 子串的主要内容,如果未能解决你的问题,请参考以下文章

P2679 子串

P2679 子串

洛古 P2679 子串 题解

P2679 子串

Luogu P2679 子串(字符串+dp)

P2679 子串