本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
【问题描述】
有两个仅包含小写英文字母的字符串 A 和 B。现在要从字符串 A 中取出 k 个 互不重叠 的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出的位置不同也认为是不同的方案 。
【输入格式】
输入文件名为 substring.in。
第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问题描述中所提到的 k,每两个整数之间用一个空格隔开。
第二行包含一个长度为 n 的字符串,表示字符串 A。
第三行包含一个长度为 m 的字符串,表示字符串 B。
【输出格式】
输出文件名为 substring.out。
输出共一行,包含一个整数,表示所求方案数。由于答案可能很大,所以这里要求输出答案对 1,000,000,007 取模的结果。
【输入输出样例 1】
substring.in
6 3 1
aabaab
aab
substring.out
2
见选手目录下 substring/substring1.in 与 substring/substring1.ans。
【输入输出样例 2】
substring.in
6 3 2
aabaab
aab
substring.out
7
见选手目录下 substring/substring2.in 与 substring/substring2.ans。
【输入输出样例 3】
substring.in
6 3 3
aabaab
aab
substring.out
7
见选手目录下 substring/substring3.in 与 substring/substring3.ans。
【输入输出样例说明】
所有合法方案如下:
(加下划线的部分表示取出的子串)
样例 1:aab aab / aab aab
样例 2:a ab aab / a aba ab / a a ba ab / aab a ab
aa b aab / aa baa b / aab aa b
样例 3:a a b aab / a a baa b / a ab a a b / a aba a b
a a b a a b / a a ba a b / aab a a b
【输入输出样例 4】
见选手目录下 substring/substring4.in 与 substring/substring4.ans。
【数据规模与约定】
对于第 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
正解:
这道题就是NOIP2015的day2T2,个人认为是一道非常有水平的DP好题,考察了对DP的综合运用,状态的设计、转移都是DP中的经典,同时前缀和优化和滚动数组的使用也很重要。
前几天我还把整个NOIP出现过的DP题给麓山信息组和我们信息组一批人讲解过一遍,这道题自然是最难的。正好自己也总结一下。
很容易想到这道题中A串每个字母只有三种可能:未被选入与B串匹配、是某一个选出来的子串的开头、是某一个选出来的子串的中间。那么我设计的状态肯定要考虑第几个串、A匹配到谁、B匹配到谁。所以我选择设计的状态为:f[kk][i][j],表示当前A选取到第kk个串,而且A串当前是i与B串的j匹配的方案数。显然A串的i若和B串的j不相等这个方案数就为0。下面考虑转移方程和转移对象。因为当前有三种可能,那么不选的情况可以不管,我们只需要管有多少种选的方案。那么就只剩两种可能了:当前这一位新开始了一个子串,或者紧接着上一个字母仍是上一个子串。
也就是说假如新开启了一个子串,我们需要枚举上一个子串的末尾;如果紧接着那只能从上一个转过来。
根据上面提到的,列出转移式:
$${f[kk][i][j]=\sum_{0<=l<i}^{s[i]==ch[j]}f[kk-1][l][j]}$$
$${=\sum_{0<=l<i}^{s[i-1]==ch[j-1]}f[kk-1][l][j] +f[kk][i-1][j-1]}$$
可以看出如果枚举l然后转移的话,复杂度是O(n^2 m k),并不能通过所有数据点。那怎么办呢,我们考虑这个前缀和我们没有必要每次都从头for到尾,加之我们之前已经做过了,我们不妨把这个前缀和记录下来,这样可以做到O(1)转移。
现在时间上是没问题了,但是空间却开不下,注意到转移式子中对于第k个子串,我们只需要调用第k-1个子串的信息,这也就意味着我们只需保留上一次的方案即可,再久一点可以不管了,容易想到滚动数组,可以把空间上降一个维度,具体见代码实现。
1 //It is made by ljh2000 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 using namespace std; 14 typedef long long LL; 15 #define RG register 16 const int inf = (1<<30); 17 const int MOD = 1000000007; 18 const int MAXN = 1011; 19 const int MAXM = 211; 20 const int MAXK = 211; 21 LL f[2][MAXN][MAXM],S[2][MAXN][MAXM],ans;//f[k][i][j]表示取到第k个串,A串匹配到i,B串匹配到j的方案数 22 int n,m,k; 23 char s[MAXN],ch[MAXM]; 24 25 inline int getint() 26 { 27 RG int w=0,q=0; RG char c=getchar(); 28 while((c<'0' || c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); 29 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); return q ? -w : w; 30 } 31 32 inline void work(){ 33 n=getint(); m=getint(); k=getint(); 34 scanf("%s",s+1); scanf("%s",ch+1); 35 S[0][0][0]=1; for(RG int i=1;i<=n;i++) S[0][i][0]=1;//记录前缀和 36 f[0][0][0]=1;//只能从0开始转过来 37 RG int tag=0; 38 for(RG int kk=1;kk<=k;kk++) { 39 tag^=1; memset(S[tag],0,sizeof(S[tag])); memset(f[tag],0,sizeof(f[tag])); 40 for(RG int l1=1;l1<=n;l1++) { 41 for(RG int l2=1;l2<=min(m,l1);l2++) { 42 if(s[l1]!=ch[l2]) { S[tag][l1][l2]=S[tag][l1-1][l2]; if(S[tag][l1][l2]>=MOD) S[tag][l1][l2]%=MOD; continue; } 43 f[tag][l1][l2]=S[tag^1][l1-1][l2-1]; 44 if(s[l1-1]==ch[l2-1] && l1!=1 && l2!=1) f[tag][l1][l2]+=f[tag][l1-1][l2-1]; 45 S[tag][l1][l2]=S[tag][l1-1][l2]+f[tag][l1][l2]; 46 if(f[tag][l1][l2]>=MOD) f[tag][l1][l2]%=MOD; 47 if(S[tag][l1][l2]>=MOD) S[tag][l1][l2]%=MOD; 48 } 49 } 50 } 51 for(RG int i=1;i<=n;i++) ans+=f[tag][i][m],ans%=MOD; 52 printf("%lld",ans); 53 } 54 55 int main() 56 { 57 work(); 58 return 0; 59 }