• [NOIP2015] 子串 题解


    这题目第一眼扫过去就是DP,状态的表示熟练的人也不能想出来用$f[i,j,k]$表示当$A$匹配到前$i$位,$B$字符串匹配到$j$位,将$A$序列分成$k$段时候所能得到的数量。

    总共有两种情况:
    一:$i$这一位不与$j$匹配,即用前$i-1$去匹配$B$的前$j$位;二:$i$这一位与$i$前几位能与$B$的$j$这一位与其前几位一起匹配,作为一个单独的段(也就是有相同的后缀)。那么就有$f[i,j,k]=f[i-1][j][k]$(不匹配)$+sum_{L}f[i-L-1][j-L-1][k-1]$(满足$A[i-L..i]=B[j-L..j]$,也就是所说的一起匹配)。

    那么注意好初始化不难写出一个朴素的代码:

    for(int i=0;i<=n;++i) f[i][0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m && j<=i;j++){
            for(int p=1;p<=k && p<=j;p++){
                f[i][j][p]+=f[i-1][j][p];
                for(int l=0;l<i && l<j && A[i-l]==B[j-l];l++)
                    f[i][j][p]+=f[i-l-1][j-l-1][p-1];
            }
        }
    }

    可惜,这样写会挂掉,时间和空间上都是。先考虑时间上的优化,可以优化的地方只剩下循环找一起匹配的段哪里了。我们想如果$A[i-L..i]=B[j-L..j]$的话,那么$f[i-1,j-1,k]$是不是保存了求$sum_{L}f[i-L-1][j-L-1][k-1]$的那一段呢?(因为就相当于$A[i-1..i]$与$B[j-1..j]$一起匹配进去然后算)。

    但是如果这样简单的改一下代码是不行的:

    for(int i=0;i<=n;++i) f[i][0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m && j<=i;j++){
            for(int p=1;p<=k;p++){
                f[i][j][p]+=f[i-1][j][p];
                if(A[i]==B[j]){
                    f[i][j][p]+=f[i-1][j-1][p-1];
                       if(A[i-1]==B[j-1])
                           f[i][j][p]+=f[i-1][j-1][p];
                }
            }
        }
    }

    因为这里的$f$数组保存的信息不一定能构成连续,它也会保存前面不能连续匹配的数据,比如这组数据:

    6 3 2
    aabaab
    aab
    按照上面代码跑出来的$f[4,1,1]=3$这本身没错,但是看$f[5,2,2]=f[4,2,2]+f[4,1,0]+f[4,1,1]=1+0+3=4$,显然有误。仔细思考就会发现我们可以新建一个数组$g$,当$A[i]!=B[j]$的时候把$g$置为$0$,当$A[i]=B[j]$的时候令$g[i][j][k]=g[i-1][j-1][k]$(和前面连续的一同构成这第k段)$+f[i-1][j-1][k-1]$(单独构成第k段),而$f[i][j][k]=g[i][j][k]+f[i-1][j][k]$就行了。

    然后就是空间的优化了,像背包一样把第一维省略掉,第二位倒序循环就ok了。

    最后附上代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1005,M=205,mod=1000000007;
    typedef long long ll;
     
    char A[N],B[N];
    ll f[M][M],g[M][M];
    int n,m,k;
     
    int main(){
        scanf("%d%d%d",&n,&m,&k);
        scanf("%s",A+1);
        scanf("%s",B+1);
        f[0][0]=1;
        for (int i=1;i<=n;i++){
            for(int j=min(m,i);j>=1;j--) {
                for(int p=1;p<=k && p<=j;p++) {
                    if(A[i]==B[j])  g[j][p]=(g[j-1][p]+f[j-1][p-1])%mod;
                    else g[j][p]=0;
                    f[j][p]=(g[j][p]+f[j][p])%mod;
                }
            }
        }
        printf("%lld",f[m][k]%mod);
        return 0;
    }
  • 相关阅读:
    给伪类设置z-index= -1;
    UITextView的字数限制 及 添加自定义PlaceHolder
    UIView 翻转动画
    viewController的自动扩展属性导致TableViewGroupStyle时向上填充
    指针属性直接赋值 最好先retain 否则内存释放导致crash
    UIButton 点击后变灰
    IOS 设置透明度导致底层View始终可见
    UIDatePicker 时间选择器
    放大Button热区的方法哟
    数组套字典排序
  • 原文地址:https://www.cnblogs.com/Asika3912333/p/11753253.html
Copyright © 2020-2023  润新知