传送门
在赛场上这道题坑了我不少的时间……还是我太菜了,不过还好写出来了(可我还是被卡常了……90分,我优化了一下常数就过了……不开心),时间复杂度O(nmk)。
DP的思路很简单
f(k,i,j)表示分了k段,用了第一个串中的前i个数字,已经构成了第二个串的前j个的方案数
f(k,i,j)=⎧⎩⎨∑f(k−1,l,j−1)∑f(k−1,l,j−1)+f(k,i−1,j−1)当s1[i]==s2[j]且s1[i−1]!=s2[j−1]当s1[i]==s2[j]且s1[i−1]==s2[j−1]其中0<l<i其中0<l<i
第一个转移是独立开辟出一个部分的可能数,第二个是算上之前就已经分了k个部分后把第k个部分扩大的方案数。还是比较好理解的,当然f数组要滚动第一维,不然会爆内存。
我觉得这一道题的难度,都和当年乌龟棋在当时的难度差不多了。但这道题的70分算法O(nm2k)比较好写,所以拉不卡差距。
相信大家在看了我的状态转移之后能想出O(nmk)的方法。其实我们需要维护的就只是那一个∑用一个tmp数组保存一下就好了(详见代码)
代码
#include<cstdio>
#include<cstring>
typedef unsigned int LL;
const LL MOD = 1000000007LL;
const int MAXN = 1005;
int n, m, K;
LL f[2][MAXN][205];
LL tmp[2][MAXN][205];
char s1[MAXN], s2[MAXN];
LL ans = 0;
int main()
{
scanf("%d%d%d", &n, &m, &K);
scanf("%s%s", s1+1, s2+1);
f[0][0][0] = 1;
tmp[0][0][0] = 1;
for(int i = 0; i <= n; i ++)
tmp[0][i][0] = 1;
for(int k = 1; k <= K; k ++) {
memset(tmp[k&1], 0, sizeof (tmp[k&1]));
memset(f[k&1], 0, sizeof (f[k&1]));
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
if(s1[i] == s2[j]) {
f[k&1][i][j] = tmp[(k+1)&1][i-1][j-1];
if(s1[i-1] == s2[j-1]) f[k&1][i][j] = (f[k&1][i][j] + f[k&1][i-1][j-1]) % MOD;
if(f[k&1][i][j] >= MOD) f[k&1][i][j] -= MOD;
}
tmp[k&1][i][j] = (tmp[k&1][i][j] + f[k&1][i][j]) % MOD + tmp[k&1][i-1][j];
if(tmp[k&1][i][j] > MOD) tmp[k&1][i][j] -= MOD;
}
}
}
for(int i = 1; i <= n; i ++)
ans = (ans + f[K&1][i][m]) % MOD;
printf("%d\n", ans);
return 0;
}