P2679 [NOIP2015 提高组] 子串 题解

文章介绍了如何通过动态规划求解给定字符串A中选取k个子串形成与B等价的字符串的方案数,涉及了状态转移和优化技巧。
摘要由CSDN通过智能技术生成

原题目:P2679 [NOIP2015 提高组] 子串 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目背景

NOIP2015 Day2T2

题目描述

有两个仅包含小写英文字母的字符串 A 和 B。

现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 B 等?

注意:子串取出的位置不同也认为是不同的方案。

输入格式

第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问题描述中所提到的 k,每两个整数之间用一个空格隔开。

第二行包含一个长度为 n 的字符串,表示字符串 A。

第三行包含一个长度为 m 的字符串,表示字符串 B。

输出格式

一个整数,表示所求方案数。

由于答案可能很大,所以这里要求输出答案对 1000000007取模的结果。

说明/提示

数据范围

对于第 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。

90分三维数组

 f[i][j][x]表示从A字符串中拿出i个数组合成x个子串再组成B字符串的前j个数(i<=n,j<=m,x<=k)

当ai != bj时,f[i][j][x]=f[i-1][j][x],即不选这个数,将前一个数的方案数传递

当ai == bj时,比对从i的位置的数起,如果前y个数能一一匹配的数的方案数 (ai-1 == bj-1),那就将它们的值累加起来,赋值给f[i][j][x],再加上不选这个数的方案数

Ac代码如下

#include <bits/stdc++.h>
#define mo 1000000007
using namespace std;
int n,m,k,sum;
string a,b;
int f[1005][105][105];
int main ()
{
	int i,j,x,y;
	cin>>n>>m>>k;
	cin>>a>>b;
	a=" "+a;
	b=" "+b;
	f[0][0][0]=1;
	for (i=1;i<=n;i++)
	{
		for (j=0;j<=m;j++)
		{
			for (x=0;x<=k;x++)
			{
				int sum=0;
				if (a[i]==b[j])
				{
					for (y=1;y<=j && i-y>=0;y++)
					{
						if (a[i-y+1]==b[j-y+1]) sum=(f[i-y][j-y][x-1]+sum)%mo;
						else break;
					}
				}
				f[i][j][x]=(f[i-1][j][x]+sum)%mo;
			}
		}
	}
	cout<<f[n][m][k];
	return 0;
}

100分二维数组

因为f[i][j][x]为前y个数累加的值,即

f[i][j][x]=f[i-1][j-1][x-1] + f[i-2][j-2][x-1] + ……

若ai+1 == bj+1,那么

f[i+1][j+1][x]=f[i][j][x-1] + f[i-1][j-1][x-1] + ……

我们发现,在上面的两个式子中有一串个相等的值 f[i-1][j-1][x-1] + f[i-2][j-2][x-1] + ……,我们用一个数组 sum[i][j][x] 记录下它,就节省了一次循环,我们再将sum[i][j][x]代入上方的式子

如果ai == bj,那么

sum[i][j][x]=sum[i-1][j-1][x] + f[i-1][j-1][x-1]

f[i][j][x]=f[i-1][j][x] + sum[i][j][x]

如果ai != bj,那么

sum[i][j][x]=0

f[i][j][x]=f[i-1][j][x]

由上面的式子可以看出,i只与i-1有关,与i-2 ……无关,所以我们可以将i优化

Ac代码如下

#include <bits/stdc++.h>
#define mo 1000000007
using namespace std;
int n,m,k;
string a,b;
int f[2][205][205],sum[2][205][205];
int main ()
{
	int i,j,x;
	cin>>n>>m>>k;
	cin>>a>>b;
	a=" "+a;
	b=" "+b;
	f[0][0][0]=1;
	for (i=1;i<=n;i++)
	{
		for (j=0;j<=m;j++)
		{
			for (x=0;x<=k;x++)
			{
				if (a[i]==b[j]) sum[i%2][j][x]=(sum[(i+1)%2][j-1][x]+f[(i+1)%2][j-1][x-1])%mo;
				else sum[i%2][j][x]=0;
				f[i%2][j][x]=(f[(i+1)%2][j][x]+sum[i%2][j][x])%mo;
			}
		}
	}
	cout<<f[n%2][m][k]%mo;
	return 0;
}

这里我用i%2和(i+1)%2来切换i与i-1层,近似可以看作两个一维数组,或者可以把他当作01背包,倒序循环来切换,便于理解我就写为上方的代码

成功Ac!!!

制作不易请多多点赞!!!

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值