[NOIP2015 提高组] 子串

题目描述

有两个仅包含小写英文字母的字符串 \(A\)\(B\)

现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 \(B\) 相等?

注意:子串取出的位置不同也认为是不同的方案。

输入格式

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

第二行包含一个长度为 \(n\) 的字符串,表示字符串 \(A\)

第三行包含一个长度为 \(m\) 的字符串,表示字符串 \(B\)

输出格式

一个整数,表示所求方案数。

由于答案可能很大,所以这里要求输出答案对 \(1000000007\) 取模的结果。

样例输入 #1

6 3 1 
aabaab 
aab

样例输出 #1

2

样例输入 #2

6 3 2 
aabaab 
aab

样例输出 #2

7

样例输入 #3

6 3 3 
aabaab 
aab

样例输出 #3

7

提示

对于第 1 组数据:\(1≤n≤500,1≤m≤50,k=1\);
对于第 2 组至第 3 组数据:\(1≤n≤500,1≤m≤50,k=2\);
对于第 4 组至第 5 组数据:\(1≤n≤500,1≤m≤50,k=m\);
对于第 1 组至第 7 组数据:\(1≤n≤500,1≤m≤50,1≤k≤m\);
对于第 1 组至第 9 组数据:\(1≤n≤1000,1≤m≤100,1≤k≤m\);
对于所有 10 组数据:\(1≤n≤1000,1≤m≤200,1≤k≤m\)

Solution:

状态表示:\(f[i, j, k]\)表示只用\(A\)的前\(i\)个字母,选取了\(k\)段,可以匹配\(B\)的前\(j\)个字母的方案数。

状态计算:\(f[i, j, k]\)表示的所有方案分成两大类:

  1. 不用\(S[i]\),则方案数是 \(f[i - 1, j, k]\)
  2. 使用\(S[i]\),那么可以按\(S[i]\)所在的一段一共有多少字母继续分类:

\[如果有t个字母,则方案数是f[i - t, j - t, k - 1] \]

时间复杂度: \(O(nm2k)\)


时间优化:

我们发现f[i, j, k]第二项的表达式和f[i - 1, j - 1, k]第二项的表达式很像,具有递推关系,因此可以令sum[i, j, k] = sum(f[i - t, j - t, k]),则:

如果 S[i] == T[j],那么 sum[i, j, k] = sum[i - 1, j - 1, k] + f[i - 1, j - 1, k - 1];
如果 S[i] != T[j],那么 sum[i, j, k] = 0;
至此,时间复杂度可以降至 \(O(nmk)\)

空间优化:

空间上需要 \(2nmk\) 的内存,总共需要 \(2×1000×20022×1000×2002\)个int,约 \(152MB\),超过了内存限制。

仔细观察转移方程,发现f[i, j, k]和sum[i, j, k]均只和第i - 1层有关,因此可以使用滚动数组,同时可以发现j和k只会从更小的值转移过来,因此可以使用类似于01背包问题优化空间的方式,从大到小枚举j, k,这样连滚动都可以省略了。(参考AcWing)

Code:

#1

#include <iostream>
#include <string>

using namespace std;

const int N=1005,M=205,mod=1000000007;

int n,m,K,ans;
int f[N][M][M];
string a,b;

int main()
{
    cin>>n>>m>>K>>a>>b;
    a=" "+a;b=" "+b;

    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 k=1;k<=K && k<=j;++k)
            {
                f[i][j][k]=f[i-1][j][k];
                for(int t=1;i>=t && j>=t;++t)
                    if(a.substr(i-t+1,t)==b.substr(j-t+1,t))
                        f[i][j][k]=(f[i][j][k]+f[i-t][j-t][k-1])%mod;
            }
    }
    printf("%d\n",f[n][m][K]);
    return 0;
}

#2

#include <iostream>
#include <string>

using namespace std;

const int N=1005,M=205,mod=1000000007;

int n,m,K,ans;
int f[M][M],s[M][M];
string a,b;

int main()
{
    cin>>n>>m>>K>>a>>b;
    a=" "+a;b=" "+b;

    f[0][0]=1;
    for(int i=1;i<=n;++i)
        for(int j=m;j>=1;--j)
            for(int k=K;k>=1;--k)
            {
                if(a[i]==b[j])
                    s[j][k]=(s[j-1][k]+f[j-1][k-1])%mod;
                else
                    s[j][k]=0;
                f[j][k]=(f[j][k]+s[j][k])%mod;
            }
    printf("%d\n",f[m][K]);
    return 0;
}
posted @ 2022-10-04 21:27  FighterQ  阅读(30)  评论(0)    收藏  举报