https://www.luogu.org/blog/frankchenfu/solution-p2679
https://blog.sengxian.com/solutions/noip-2015-day2
主要考察动态规划的思想。
设 f[i][j][k]f[i][j][k] 为串 AA 匹配到第 i - 1i−1 个位置(位置从 00 开始),串 BB 匹配到 j - 1j−1 个位置,而且恰好选串 AA 的 i - 1i−1 号字符的方案总数。
设 s[i][j][k]s[i][j][k] 为串 AA 匹配到第 i - 1i−1 个位置(位置从 00 开始),串 BB 匹配到 j - 1j−1 个位置,的方案总数。可以知道 s[m][n][k]s[m][n][k]就是要求的答案。
考虑边界,空空相对,00 组有一种方法,所以有 s[i][0][0] = 1s[i][0][0]=1。
接下来考虑状态转移方程:s[i][j][k]s[i][j][k] 是很容易求出来的。
s[i][j][k] = s[i-1][j][k] + f[i][j][k]s[i][j][k]=s[i−1][j][k]+f[i][j][k]
很好理解,s[i][j][k]s[i][j][k] 的方案数目就等于上一个阶段(串 AA 还没有使用第 i - 1i−1 个位置) 的方案数目,加上串 AA 使用第 i - 1i−1 个位置的方案总数。
怎么求解 f[i][j][k]f[i][j][k] 呢?这里运用了类似的最长公共子序列的思想。
如果 A[i-1] = B[j-1]A[i−1]=B[j−1],那么串 AA 的 i - 1i−1 位置就可以跟 i - 2i−2 位置合成一组(如果可能的话),这时组数不变;也可以不跟 i - 2i−2 位置合成一组,自成一组,这时组数+1。所以
f[i][j][k] = f[i-1][j-1][k] + s[i-1][j-1][k-1]\quad(A[i-1] = B[j-1])f[i][j][k]=f[i−1][j−1][k]+s[i−1][j−1][k−1](A[i−1]=B[j−1])
如果 呢?根据定义,f[i][j][k]f[i][j][k] 要恰好选串 AA 的 i - 1i−1 号字符,现在选不了,自然为 00。所以
f[i][j][k] = 0f[i][j][k]=0
综上所述,得到完整的状态转移方程:
最后数组压到二维的就可以省空间了。
代码
这是不压二维的版本,虽然会爆内存,但为了防止三维转二维的某些细节造成困扰,还是发出来。
```
// Created by Sengxian on 11/21/15.
// Copyright (c) 2015年 Sengxian. All rights reserved.
// DP
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxn][maxm][maxm], s[maxn][maxm][maxm];
void solve() {
s[0][0][0] = 1;
for(int i = 1; i <= n; ++i) {
s[i][0][0] = 1; //注意如果不压到二维,这是需要的
for(int j = 1; j <= m; ++j)
if(stra[i-1] == strb[j-1]) {
for(int k = 1; k <= min(K, j); ++k) {
f[i][j][k] = (s[i-1][j-1][k-1] + f[i-1][j-1][k]) % moder,
s[i][j][k] = (s[i-1][j][k] + f[i][j][k]) % moder;
}
}else for (int k = 1; k <= min(K, j); ++k) s[i][j][k] = s[i-1][j][k]; //注意如果不压到二维,这是需要的
}
printf("%d\n", s[n][m][K]);
}
int main() {
scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
solve();
return 0;
}
压缩后
// Created by Sengxian on 11/22/15.
// Copyright (c) 2015年 Sengxian. All rights reserved.
// dp after zip
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1000 + 5, maxm = 200 + 5, moder = 1000000007;
int m, n, K;
char stra[maxn], strb[maxm];
int f[maxm][maxm], s[maxm][maxm];
void solve() {
s[0][0] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = m; j > 0; --j)
if(stra[i-1] == strb[j-1]) {
for(int k = min(K, j); k > 0; --k) {
f[j][k] = (s[j-1][k-1] + f[j-1][k]) % moder,
s[j][k] = (s[j][k] + f[j][k]) % moder;
}
}else fill(f[j], f[j] + min(K, j) + 1, 0);
}
printf("%d\n", s[m][K]);
}
int main() {
scanf("%d%d%d%s%s", &n, &m, &K, stra, strb);
solve();
return 0;
}