题目描述 Description
有两个仅包含小写英文字母的字符串A和B。现在要从字符串A中取出k个互不重叠的非空子串,然后把这k个子串按照其在字符串A中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串与字符串B相等?注意:子串取出的位置不同也认为是不同的方案。
输入描述 Input Description
第一行是三个正整数n,m,k,分别表示字符串A的长度,字符串B的长度,以及问题描述中所提到的k,每两个整数之间用一个空格隔开。
第二行包含一个长度为n的字符串,表示字符串A。 第三行包含一个长度为m的字符串,表示字符串B。
输出描述 Output Description
输出共一行,包含一个整数,表示所求方案数。由于答案可能很大,所以这里要求输出答案对1,000,000,007取模的结果。
样例输入 Sample Input
【Input1】
6 3 1
aabaab
aab
【Input2】
6 3 2
aabaab
aab
【Input3】
6 3 3
aabaab
aab
样例输出 Sample Output
【Output1】
2
【Output2】
7
【Output3】
7
数据范围及提示 Data Size & Hint
对于第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。
题解: 字符串dp
刚开始写了一个比较暴力的dp,最坏情况下时间复杂度是O(m^3*n)
f[k][i][j] 表示取第k个子串,用s1的前i个字符匹配,到s2的第j个字符。
为什么这么表示,是因为s2的是一定都要匹配上的,但是s1中的不用。
预处理一个数组pd[i][j]表示s1[i],s2[j]开始向前匹配最多匹配的个数。
f[k][i][j]+=f[k][i-1][j]
for (int l=1;l<=pd[i][j];l++)
f[k][i][j]+=f[k-1][i-l][j-l]
这个dp的正确性十分显然。然而只能过80分。。。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 1003
#define M 203
#define p 1000000007
using namespace std;
int f[3][N][M],pd[N][M];
int n,m,k;
char s[N],s1[N];
void clear(int x)
{
for (int i=0;i<=n;i++)
for (int j=0;j<=m;j++)
f[x][i][j]=0;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d%d",&n,&m,&k);
scanf("%s",s+1);
scanf("%s",s1+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int l=i; int r=j; int t=0;
while (s[l]==s1[r]&&l>0&&r>0) l--,r--,t++;
pd[i][j]=t;
}
//for (int i=1;i<=n;i++)
// for (int j=1;j<=n;j++)
//cout<<i<<" "<<j<<" "<<pd[i][j]<<endl;
for (int i=0;i<=n;i++)
f[0][i][0]=1;
for (int t=1;t<=k;t++)
{
int x=t%2;
clear(x);
for (int i=1;i<=n;i++)
{
int last=min(m,i);
for (int j=1;j<=last;j++)
{
f[x][i][j]=(f[x][i][j]+f[x][i-1][j])%p;
for (int l=1;l<=pd[i][j];l++)
f[x][i][j]=(f[x][i][j]+f[(t-1)%2][i-l][j-l])%p;
}
}
}
int ans=f[k%2][n][m];
//for (int i=1;i<=n;i++)
// ans=(ans+f[k%2][i][m])%p;
printf("%d\n",ans);
}
枚举s1中的每一位t
f[1][i][j] 表示匹配到s2的第i位,选用了j个子串,且t一定入选。
f[0][i][j] 表示匹配到s2的第i位,选用了j个子串,t不一定入选(可选可不选,也就是总答案)
我们分情况讨论
s1[t]==s2[i]
f[1][i][j]=f[0][i-1][j-1]+f[1][i-1][j] 当前位单独成为一个子串,或与上一位合并。
f[0][i][j]=f[0][i][j]+f[1][i][j]
s1[t]!=s2[i]
f[1][i][j]=0
咦,貌似少了s1所在的那一维,这样真的可以推吗?
我们向01背包一样s2倒叙枚举,s1正序枚举,这样每次直接在上一次的结果上覆盖,并且保证s1中的每个点不会重复利用。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 1003
#define M 203
#define p 1000000007
using namespace std;
int f[3][N][M],pd[N][M],sum[3][N][M];
int n,m,k;
char s[N],s1[N];
void clear(int x)
{
for (int i=0;i<=n;i++)
for (int j=0;j<=m;j++)
f[x][i][j]=0,sum[x][i][j]=0;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d%d",&n,&m,&k);
scanf("%s",s+1);
scanf("%s",s1+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int l=i; int r=j; int t=0;
while (s[l]==s1[r]&&l>0&&r>0) l--,r--,t++;
pd[i][j]=t;
}
memset(f,0,sizeof(f));
f[0][0][0]=1;
for (int i=1;i<=n;i++)
for (int j=m;j>=1;j--)
{
if (s[i]==s1[j])
{
for (int t=1;t<=min(j,k);t++)
f[1][j][t]=(f[0][j-1][t-1]+f[1][j-1][t])%p,
f[0][j][t]=(f[0][j][t]+f[1][j][t])%p;
}
else for (int t=1;t<=min(j,k);t++)
f[1][j][t]=0;
}
printf("%d\n",f[0][m][k]);
}
本文针对NOIP2015第二天第二题“子串”进行解答,介绍了一种时间复杂度为O(m^3*n)的暴力DP解法及其优化方案。最终提供了一种更为高效的DP解法,通过枚举和状态转移实现了对问题的有效求解。
799

被折叠的 条评论
为什么被折叠?



