Noip 2015 子串(洛谷P2679)

题目描述

有两个仅包含小写英文字母的字符串 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:
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。


这是我见过的题中,代码长度与思维难度比例差距最大的一道神题!
代码真的短的惊人,但是思维难度真的不是一般的大。

首先这一看就是一道动态规划吧。

第一眼看到这题的时候,想到的DP式子是这样的:
dp[ i ][ j ][ k ]代表A串位置到了 i ,B串到了 j ,已经用了 k 个子串。
自然想到: dp[ i ][ j ][ k ] = dp[ i-1 ][ j-1 ][ k ] + dp[ i-1 ][ j-1 ][ k-1 ]; ( A[i] == B[j] )
即:能匹配时,方案数为:单独使用当前字符为一个子串 + 与前面相连形成一个子串;

稍微仔细一想就可以知道,这个DP式子是有问题的。
如果不使用当前字符,情况是什么样的呢?貌似被默默的遗漏了耶……

所以我们就要分开来设了。
设s[ i ][ j ][ k ]为A用到了 i ,B用到了 j ,已经用了 k 个子串, 并且一定用了当前字符(A[i])时的方案数。
设f[ i ][ j ][ k ]为A用到了 i ,B用到了 j ,已经用了 k 个子串, 无论用不用当前字符(A[i])时的方案数总和

接下来这个转移可就有蛮难想了。
一个一个来,

先分析一下 s 的转移。
能转移的前提自然是 A[ i ] == B [ j ]啦。
既然 A[i] 一定要用,那么依旧是两种情况:独自成一串与前面的成一串
独自成一串,方案数为:f[ i-1 ][ j-1 ][ k-1]
与前方共成一串,方案数为:s[ i-1 ][ j-1 ][ k ],因为前一个字符串(A[i-1])也一定要用!
所以合并一下: s[ i ][ j ][ k ] = f[ i-1 ][ j-1 ][ k-1 ] + s[ i-1 ][ j-1 ][ k ];

接着分析 f 的转移。
f[ i ][ j ][ k ] 的来源也有两种: 使用当前字符不使用当前字符
对于使用当前字符,方案数算法如上,答案即:s[ i ][ j ][ k ];
对于不使用当前字符,则从f[ i-1 ]转来,即:f[ i -1 ][ j ][ k ];
合并一下: f[ i ][ j ][ k ] = f[ i-1 ][ j ][ k ] + s[ i ][ j ][ k ];

所以将两个合并一下子,就得到:

-->对于每一个i , j , k :
if( A[i] == B[j] )s[i][j][k] = f[i-1][j-1][k-1] + s[i-1][j-1][k];
else s[i][j][k] = 0;
f[i][j][k] = f[i-1][j][k] + s[i][j][k];

答案存在f[ n ][ m ][ k ]中,显然边界条件为 f[ i ][ 0 ][ 0 ] = 1;

到这里这道题就做完了。
别看上面说的顺溜溜的,笔者做的时候整个脑子都是混乱的!
当然直接开O(n*m*k)的数组是肯定开不下的,第一维的 i 显然可以滚掉(对于提高组大佬这肯定是so easy 啦)。
下面是具体实现代码:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;

int a,b,k;
char A[1002],B[1002];
int f[3][1000][1000],s[3][1000][1000];

int main()
{

    scanf("%d%d%d\n",&a,&b,&k);
    scanf("%s",A+1);  scanf("%s",B+1);
    int now=1,past=0; 
    f[0][0][0]=1;
    for(int e=1;e<=a;e++){
        f[now][0][0]=1;
        for(int j=1;j<=b;j++){
            for(int t=1;t<=k;t++){
                if(A[e]==B[j])s[now][j][t]=(s[past][j-1][t]+f[past][j-1][t-1])%mod;
                else s[now][j][t]=0;
                f[now][j][t]=(f[past][j][t]+s[now][j][t])%mod;
            }
        }swap(now,past);
    }
    cout<<f[past][b][k];
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值