UOJ149 【NOIP2015】子串

本文作者: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 }
原文地址:https://www.cnblogs.com/ljh2000-jump/p/5966642.html