【noip】子串

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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值