错题集5——子序列和子串

题目信息

对于字符串s,有些同学经常搞混 子串 和 子序列:

子串:s中连续的一段字符,例如"abc"的子串有{a,b,c,ab,bc,abc,空}等
子序列:s中相对顺序不变的一段字符,例如"abc"的子序列有{a,b,c,ab,ac,bc,abc,空}等

题目描述

现在给定字符串A,B,请你删除B串的一个最短子串,使B串成为A串的一个子序列。

答案可能为0,即B本身就是A的一个子序列;答案也可能为lenB,即需要将B删成空串。

输入格式

第一行1个整数t,代表输入方式

若t = 0: 输入2行,分别为字符串A,B

若t = 1: 输入2行,每行4个非负整数{c1, a, b, n},用以下方式自行生成A串和B串:

long long c[MAXN];
char str[MAXN];
c[1] = c1;
for i = 2 to n:
    c[i] = (c[i-1]*a + b) % P;
for i = 1 to n:
    str[i] = 'a' + c[i]%26

其中模数P = 1e9+7

输出格式

一个整数,代表所需要删除的最短子串长度

输入输出样例

输入 #1
0
im
ran
输出 #1
3
输入 #2
0
abca
accepted
输出 #2
6
输入 #3
0
abacaba
aa
输出 #3
0
输入 #4
1
0 2 3 15
0 4 5 5
输出 #4
1
说明/提示
对于样例4,A串/B串为:

adjvtphrlzbfndj
afzbj

  • 对于20%的数据,字符串长度<=20
  • 对于40%的数据,字符串长度<=200
  • 对于60%的数据,字符串长度<=2000
  • 对于80%的数据,字符串长度<=1e5
  • 对于100%的数据,字符串长度<=1e7,输入保证仅包含小写字符,若字符串长度<=2000则有tp=0,否则有tp=1;a,b<=1e9

解题思路

法1:二分+判断子序列(60pts)

二分删除的最小长度,然后枚举所有这个长度的B的子串判断是否为A的子序列。
时间复杂度:O(n2logn)(二分:logn 枚举子串:O(n) 双指针判断子序列:O(n))
空间复杂度:O(n)

法2:递推(正解)

  • 将A的所有前缀在B中匹配,prei为A[1到i]可以匹配到的最大的B的前缀
    则prei=prei-1 + (Ai == Bpre[i-1]+1)
  • 将A的所有后缀在B中匹配,sufi为A[i到n]可以匹配到的最大的B的后缀
    则sufi=sufi+1 + (Ai == Bsuf[i+1]-1)
  • 答案为min 1 <= i <= n {sufi+1 - prei - 1}
    时间复杂度:O(n)
    空间复杂度:O(n)

代码实现(正解)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int P = 1e9 + 7;
const int N = 10000005;
char a[N], b[N];
ll c[N];
int pre[N], suf[N];
int n, m;
int main() {
    int t;
    cin >> t;
    if (t == 0) {
        cin >> a + 1 >> b + 1;
    } else {
        // 构造字符串
        int x, y;
        cin >> c[1] >> x >> y >> n;
        a[1] = 'a' + c[1] % 26;
        for (int i = 2; i <= n; ++i) {
            c[i] = (c[i - 1] * x + y) % P;
            a[i] = 'a' + c[i] % 26;
        }
        cin >> c[1] >> x >> y >> m;
        b[1] = 'a' + c[1] % 26;
        for (int i = 2; i <= m; ++i) {
            c[i] = (c[i - 1] * x + y) % P;
            b[i] = 'a' + c[i] % 26;
        }
    }
    n = strlen(a + 1);
    m = strlen(b + 1);
    for (int i = 1; i <= n; ++i) {
        pre[i] = pre[i - 1];
        if (pre[i] < m && a[i] == b[pre[i] + 1]) {
            ++pre[i];
        }
    }
    suf[n + 1] = m + 1;
    for (int i = n; i >= 1; i--) {
        suf[i] = suf[i + 1];
        if (suf[i] > 1 && a[i] == b[suf[i] - 1]) {
            --suf[i];
        }
    }
    int res = m;
    for (int i = 0; i <= n; ++i) {
        res = min(res, suf[i + 1] - pre[i] - 1);
    }
    cout << max(res, 0) << '\n';
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒟蒻一枚

谢谢鸭~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值