【noip2015】子串

题目描述

有两个仅包含小写英文字母的字符串 A 和 B。现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一 个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出的位置不同也认为是不同的方案。


输入

第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问 题描述中所提到的 k,每两个整数之间用一个空格隔开。第二行包含一个长度为 n 的字符串,表示字符串 A。 第三行包含一个长度为 m 的字符串,表示字符串 B。


输出

输出共一行,包含一个整数,表示所求方案数。(由于答案可能很大,所以这里要求输 出答案对 1,000,000,007 取模的结果。)


样例输入

6 3 1
aabaab
aab


样例输出

2

对于所有 10 组数据:1≤n≤1000,1≤m≤200,1≤k≤m。



题解

好难啊,还好没在15年考。

解1:

设 s[ i ][ j ][ k ] 为A用到了 i ,B用到了 j ,已经用了 k 个子串, 并且一定用了当前字符 ( A[ i ] ) 时的方案数。

设 f[ i ][ j ][ k ] 为A用到了 i ,B用到了 j ,已经用了 k 个子串, 无论用不用当前字符 ( A[ i ] ) 时的方案数总和。

我也不知道为什么这么设

先考虑 s 的转移:

 当A[ i ] == B[ i ] 时,可以是A[ i ] 单独一段,也可以是A[ i ] 与前面的共成一段。

A[ i ] 单独一段:s[ i ][ j ][ k ] = f[ i-1 ][ j-1 ][ k-1 ]

A[ i ] 与前面共成一段: s[ i ][ j ][ k ] = s[ i-1 ][ j-1 ][ k ]

所以: s[ i ][ j ][ k ] = f [ i-1 ][ j-1 ][ k-1 ] + s[ i-1 ][ j-1 ][ k ]

当A[ i ] != B[ i ] 时,s[ i ][ j ][ k ] = 0

再考虑 f 的转移:

f 的转移也有两种情况,

选择A[ i ] :f [ i ][ j ][ k ] = s[ i ][ j ][ k ]

不选择A[ i ] :f [ i ][ j ][ k ] = f [ i-1 ][ j ][ k ]

所以: f [ i ][ j ][ k ] = s[ i ][ j ][ k ] + f [ i-1 ][ j ][ k ]

答案为 f [ n ][ m ][ k ] 。

由于数据较大,所以把第一维滚掉。

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long

const int maxn=1000+50;
const int maxm=200+50;
const int mod=1000000007;

int n,m,k,s[2][maxm][maxm],f[2][maxm][maxm],cur;
char a[maxn],b[maxm];

template<typename T>void read(T& aa){
    char cc; ll ff;aa=0;cc=getchar();ff=1;
    while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
    if(cc=='-') ff=-1,cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    aa*=ff;
}

int main(){
    read(n),read(m),read(k);
    cin>>a+1>>b+1;
    f[0][0][0]=1;
    for(int i=1;i<=n;i++){
        cur^=1;
        f[cur][0][0]=1;
        for(int j=1;j<=m;j++)
        for(int w=1;w<=k;w++){
            if(a[i]==b[j]) s[cur][j][w]=(s[cur^1][j-1][w]+f[cur^1][j-1][w-1])%mod;
            else s[cur][j][w]=0;
            f[cur][j][w]=(s[cur][j][w]+f[cur^1][j][w])%mod;
        }
    }
    cout<<f[cur][m][k];
    return 0;
}

解2:

四维dp,dp[ i ][ j ][ k ][ 0/1 ]表示A数组选到了 i ,B数组选到了 j,用了 k 段,选不选A[ i ] 的方案数。和方法1的转移的方式大体一样,初始化dp[ i ][ 0 ][ 0 ][ 0 ] = 1 。

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long

const int maxn=1000+50;
const int maxm=200+50;
const int mod=1000000007;

int n,m,k,dp[2][maxm][maxm][2],cur;
char a[maxn],b[maxm];

template<typename T>void read(T& aa){
    char cc; ll ff;aa=0;cc=getchar();ff=1;
    while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
    if(cc=='-') ff=-1,cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    aa*=ff;
}

int main(){
    read(n),read(m),read(k);
    cin>>a+1>>b+1;
    dp[0][0][0][0]=1;
    for(int i=1;i<=n;i++){
        cur^=1;
        dp[cur][0][0][0]=1;
        for(int j=1;j<=m;j++)
        for(int t=1;t<=k;t++){
            if(a[i]==b[j]) dp[cur][j][t][1]=((dp[cur^1][j-1][t][1]+dp[cur^1][j-1][t-1][1])%mod+dp[cur^1][j-1][t-1][0])%mod;
            else dp[cur][j][t][1]=0;
            dp[cur][j][t][0]=(dp[cur^1][j][t][0]+dp[cur^1][j][t][1])%mod;
        }
    }
    cout<<(dp[cur][m][k][1]+dp[cur][m][k][0])%mod<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/rlddd/p/9535479.html