[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]\)表示的所有方案分成两大类:
- 不用\(S[i]\),则方案数是 \(f[i - 1, j, k]\);
- 使用\(S[i]\),那么可以按\(S[i]\)所在的一段一共有多少字母继续分类:
时间复杂度: \(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;
}

浙公网安备 33010602011771号