NKOI 3559 子串

【NOIP2015 Day2】子串

Time Limit:10000MS  Memory Limit:131072K
Total Submit:2 Accepted:2 
Case Time Limit:1000MS

Description

        有两个仅包含小写英文字母的字符串A和B。现在要从字符串A中取出k个互不重叠的非空子串,然后把这k个子串按照其在字符串A中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串余字符串B相等?注意:子串取出的位置不同也认为是不同的方案。 

Input

        /*输入文件名为substring.in。*/ 
        第一行是三个正整数n,m,k,分别表示字符串A的长度,字符串B的长度,以及问题描述中所提到的k,每两个整数之间用一个空格隔开。 
        第二行包含一个长度为n的字符串,表示字符串A。 
        第三行包含一个长度为m的字符串,表示字符串B。 

Output

        /*输出文件名为substring.out。*/ 
        输出共一行,包含一个整数,表示所求方案数。由于答案可能很大,所以这里要求输出答案1,000,000,007取模的结果。 

Sample Input

样例输入1:
6 3 1
aabaab
aab

样例输入2:
6 3 2
aabaab
aab

样例输入3:
6 3 3
aabaab
aab

Sample Output

样例输出1:
2

样例输出2:
7

样例输出3:
7

Hint

输入输出样例说明: 
所有合法方案如下:(加下划线的部分表示取出的子串) 
样例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 

数据规模与约定: 
        对于第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。 

Source

感谢nodgd手打题目


首先我们看的出来这是一道动归

状态:f[k][i][j]表示用A串中的前i个字母构成了k段,已经构成了B串的前j个字符的方案总数。
f[k][i][j]=
                   ∑ f[k-1][p][j-1]                              条件:A[i]==B[j]且A[i-1]!=B[j-1]     0<p<i 
                   ∑ f[k-1][p][j-1]+f[k][i-1][j-1]     条件:A[i]==B[j]且A[i-1]==B[j-1]     0<p<i 
第一个是A[i]独立开辟出一个段的方案数
第二个是把A[i]加入之前的第k段。算上之前就已经分了k个部分后,把第k个部分扩大的方案数
时间复杂度:O(nm2k)   显然会超时
观察发现,每次都要计算前缀状态和∑ dp[k-1][p][j-1]
我们可以用一个Sum[ ][ ][ ]数组来记录下来,避免重复计算,
Sum[k][i][j] = dp[k][i][j] + Sum[k][i-1][j];
时间复杂度可降至  O(nmk) 
我们观察发现dp[k][...][...]只跟dp[k-1][...][...]有关,滚动数组处理即可!
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int mod=1000000007;
int K,n,m;
char a[1005],b[205];
int f[2][1005][205],sum[2][1005][205];
int main(){
	scanf("%d%d%d",&n,&m,&K);
	scanf("%s%s",a+1,b+1);
	int i,j,k;
	f[0][0][0]=1;
	for(i=0;i<=n;i++)
	    sum[0][i][0]=1;/初始化
	for(k=1;k<=K;k++){
		memset(sum[k&1],0,sizeof(sum[k&1]));
		memset(f[k&1],0,sizeof(f[k&1]));
		for(i=1;i<=n;i++)
		    for(j=1;j<=m;j++){
		    	if(a[i]==b[j]){
		    		f[k&1][i][j]=sum[(k-1)&1][i-1][j-1];
		    		if(a[i-1]==b[j-1])
		    		    f[k&1][i][j]=(f[k&1][i][j]+f[k&1][i-1][j-1])%mod;
				}
				sum[k&1][i][j]=(sum[k&1][i][j]+f[k&1][i][j]+sum[k&1][i-1][j])%mod;
			}
	}
	int ans=0;
	for(i=1;i<=n;i++)
	    ans=(ans+f[K&1][i][m])%mod;
	cout<<ans;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值