zoukankan      html  css  js  c++  java
  • 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 ]$,那么就不能匹配,需要继续枚举下去;

    但是当前状态并不能解决是否与前面连成一段的问题;

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

    $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 ] == 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-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(nm^2)$ 直接 $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;
    }
  • 相关阅读:
    2808 SCI中断发送
    头文件定义全局变量的几个问题
    关于多文件引用头文件变量重复定义的解决办法【BCB2010环境】
    程序中代码实现的图标,Marlett字体的对应关系
    C++ builder报错
    华硕A43系列笔记本卡解决办法
    磁盘对齐4k
    编译指令#pragma详解
    ASCII码表控制字符的确切含义
    win7无损分区轻松实现,不使用工具实现无损分区
  • 原文地址:https://www.cnblogs.com/xcg123/p/12114502.html
Copyright © 2011-2022 走看看