[NOIp提高组]子串「DP」
题目描述
有两个仅包含小写英文字母的字符串 \(A\) 和 \(B\)。
现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 \(B\) 相等?
注意:子串取出的位置不同也认为是不同的方案。
输入格式
第一行是三个正整数 \(n,m,k\),分别表示字符串 \(A\) 的长度,字符串 \(B\) 的长度,以及问题描述中所提到的 \(k\),每两个整数之间用一个空格隔开。
第二行包含一个长度为 \(n\) 的字符串,表示字符串 \(A\)。
第三行包含一个长度为 \(m\) 的字符串,表示字符串 \(B\)。
输出格式
一个整数,表示所求方案数。
由于答案可能很大,所以这里要求输出答案对 \(1000000007\) 取模的结果。
输入输出样例
输入 #1
6 3 1
aabaab
aab
输出 #1
2
说明/提示
对于所有 \(10\) 组数据:\(1≤n≤1000,1≤m≤200,1≤k≤m\)
思路分析
ps:这题常规方法不难,关键说一下优化(因为时间和空间都很容易炸)
- 这种给两个信息进行互相匹配的\(DP\)的转移状态还是比较常规的,数组记录一下两个字符串当前各自的结尾即可,另外这题要再开一维记录有多少串,所以很容易想到用数组\(f[i][j][k]\)来转移,\(i\)为\(A\)串的结尾,\(j\)为\(B\)串的结尾,\(k\)为所选串的个数
- 转移方程根据匹配情况来推即可,另外一开始理解错了题目,以为选的子串是不能连着的,结果发现是可以的,你完全可以把一个子串拆成若干个小子串……
- 首先对于 \(A[i]!=B[j]\) 的情况,很简单,就是 \(f[i-1][k]\),因为没有做贡献
- 对于 \(A[i]=B[j]\) 的情况,我们就完全可以增加一串,上面说了,这几个子串只是不能重叠,但可以连着,所以我们只需从前面 \(k-1\) 串里往前挨个转移,直到一个 \(A[i-x]!=B[j-x]\) 的为止,因为相等的都可以和 \(i\) 和 \(j\) 连起来作为一组,当然也可以全部不选
- 转移方程:
- \(f[i][j][k] = f[i-1][j][k],A[i]!=B[j]\)
- \(f[i][j][k] = f[i-1][j][k] + \sum_{x=1}^{t}f[i-x][j-x][k-1]\),\(A[t]!=B[t]且A(t,i]=B(t,j]\) (左开右闭)
- 三维空间太大,而且这样转移时间效率也很低,怎么压维和优化时间?
- 我们肯定要优先考虑将 \(i\) 这一维压掉,但是本蒟蒻想了半天也想不出怎么样,于是
看了一下题解整个傻掉 - \(i\) 不好压掉的原因在于相同的 \(j\) 和 \(k\) 在不同的i下有不同的值,如果还是像上面那个转移方程一样把区间内符合条件的挨个转移肯定是不行的,所以我们考虑整体转移,使用前缀和思想
Code
#include
#include
#include
#include
#define int long long //一年OI一场空,不开long long见祖宗
#define N 1010
#define M 210
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int mod = 1000000007;
int n,m,K,f[M][M],sum[M][M];
char a[N],b[M];
signed main(){
n = read(),m = read(),K = read();
scanf("%s%s",a+1,b+1);
f[1][0] = 1; //最初对应一种方案
for(int i = 2;i <= n+1;i++){ //2~n+1,其实是将转移滞后到了它的下一个,因为字符串是从1到n
for(int j = m+1;j >= 2;j--){ //一定要用倒序,这样你的sum数组才是从前面的i转移过来的
for(int k = K;k >= 1;k--){
sum[j][k] = a[i-1]==b[j-1] ? sum[j-1][k]+f[j-1][k-1] : 0; //即用到了前缀和思想,又用到了动态规划思想,实在是妙(就这两行我想了一晚上)
f[j][k] = (f[j][k]+sum[j][k])%mod; //因为压掉了一维,所以直接加上sum即可
}
}
}
printf("%lld\n",f[m+1][K]);
return 0;
}
DP就是这么短小精悍的东西