欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【NOIP2015】子串

程序员文章站 2024-03-20 13:16:58
...

传送门
在赛场上这道题坑了我不少的时间……还是我太菜了,不过还好写出来了(可我还是被卡常了……90分,我优化了一下常数就过了……不开心),时间复杂度O(nmk)
DP的思路很简单
f(k,i,j)表示分了k段,用了第一个串中的前i个数字,已经构成了第二个串的前j个的方案数

f(k,i,j)=f(k1,l,j1)f(k1,l,j1)+f(k,i1,j1)s1[i]==s2[j]s1[i1]!=s2[j1]s1[i]==s2[j]s1[i1]==s2[j1]0<l<i0<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;
}