• 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 }
  • 相关阅读:
    搭建Git本地服务器
    shutdown,init,halt,poweroff,reboot的区别和联系, pkill -kill -t tty7注销
    RHEL/CentOS/Fedora常用的 CentOS 5/6/7 yum 源(EPEL、Remi、RPMForge、RPMFusion, ius,163,sohu,阿里云)配置
    国内的一些开源镜像站汇总,EPEL源
    EditPlus 配置 Java & C/CPP 开发环境
    Nginx+Keepalived 做负载均衡器
    监控Nginx负载均衡器脚本
    Heartbeat+DRBD+NFS 构建高可用的文件系统
    数据库索引的作用和长处缺点
    【ThinkPHP学习】ThinkPHP自己主动转义存储富文本编辑器内容导致读取出错
  • 原文地址:https://www.cnblogs.com/ljh2000-jump/p/5966642.html
Copyright © 2020-2023  润新知