[补档计划] 字符串

  学习一个算法, 需要弄清一些地方:

    ① 问题与算法的概念;

    ② 算法, 思维轨迹

    ③ 实现, 思维轨迹;

    ④ 复杂度分析;

    ⑤ 应用.

KMP算法

字符串匹配与KMP算法

  为了方便弄清问题, 应该从特例入手.

  设 A = " ababababb " , B = " ababa " , 我们要研究下面三个递进层次的字符串匹配问题:

    ① 是否存在 A 的子串等于 B

    ② 有几个 A 的子串等于 B

    ③ A 的哪些位置的子串等于 B

  KMP算法可以直接在线性复杂度解决问题③, 进而更可以解决问题①和问题②.

KMP算法

  

  

  

  定义 next 数组, 沿着 next 往前跳, 直到匹配.

  next 数组的求法与匹配的过程相同.

KMP算法的实现

#define F(i,a,b) for (register int i=(a);i<=(b);i++)
int nS; char s[S];
int nx[S];
int nT; char t[T];
bool KMP(void) {
    for (int i=2,j=0;i<=nS;i++) {
        while (j<=nS && s[i]!=s[j+1]) j=nx[j];
        nx[i] = (s[i]==s[j+1] ? ++j : j);
    }
    for (int i=1,j=0;i<=nT;i++) {
        while (j<=nT && t[i]!=s[j+1]) j=nx[j];
        if (s[i]==s[j+1]) {
            j++;
            if (j==nT) return true;
        }
    }
    return false;
}

KMP算法的复杂度分析

  空间复杂度: O(n) .

  时间复杂度: O(n) .

    由于每次 j 只会往后移一位, 所以往后移的总位数为 O(n) .

    每次 j 往前跳, 至少会跳一位, 所以往前跳的总次数为 O(n) .

    综上, 均摊时间复杂度为 O(n) .

[XSY2034] [POI2010] Hamster

题意 给定 n 种字符串 {Si} . 求最短的字符串, 含有 m 个 {Si} .

  m <= 10^9, n <= 200, ∑|s| <= 10^6

分析 KMP +  倍增Floyd.

  两两 KMP 求增加长度, 倍增 Floyd.

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

#define F(i, a, b) for (register int i = (a); i <= (b); i++)
#define D(i, a, b) for (register int i = (a); i >= (b); i--)

#define LL long long

const int N = 205;
const int L = 100005;
const LL INF = ~0ull>>2;

int n, m, start[N], len[N], tot;
char s[L];

struct Info {
    LL go[N][N];
    friend Info operator + (Info &A, Info &B) {
        Info C;
        F(i, 1, n) F(j, 1, n) C.go[i][j] = INF;
        F(k, 1, n) F(i, 1, n) F(j, 1, n)
            C.go[i][j] = min(C.go[i][j], A.go[i][k] + B.go[k][j]);
        return C;
    }
    LL ans(void) {
        LL tiny = INF;
        F(i, 1, n) F(j, 1, n)
            tiny = min(tiny, go[i][j]);
        return tiny;
    }
}res, c;

int calc(char *A, int nA, char *B, int nB, bool tag) {
    static int nx[L];
    for (int i = 2, j = 0; i <= nB; i++) {
        while (j > 0 && B[i] != B[j+1]) j = nx[j];
        nx[i] = (B[i] == B[j+1] ? ++j : j);
    }
    if (tag) return nB-nx[nB];
    for (int i = 1, j = 0; i <= nA; i++) {
        while (j > 0 && A[i] != B[j+1]) j = nx[j];
        j += (A[i] == B[j+1]);
        if (i == nA) return nB-j;
    }
}

int main(void) {
    #ifndef ONLINE_JUDGE
        freopen("xsy2034.in", "r", stdin);
        freopen("xsy2034.out", "w", stdout);
    #endif

    static char t[L];
    scanf("%d%d", &n, &m); m--;
    F(i, 1, n) {
        scanf("%s", t+1);
        start[i] = tot+1;
        len[i] = strlen(t+1);
        F(j, 1, len[i]) s[++tot] = t[j];
    }

    F(i, 1, n) F(j, 1, n)
        res.go[i][j] = (i == j ? len[i] : INF);
    F(i, 1, n)
        F(j, 1, n)
            c.go[i][j] = calc(s+start[i]-1, len[i], s+start[j]-1, len[j], i == j);
    for (; m > 0; m >>= 1) {
        if (m%2 == 1) res = res+c;
        c = c+c;
    }
    printf("%lld\n", res.ans());

    return 0;
}

时间: 2024-10-25 02:20:24

[补档计划] 字符串的相关文章

[补档计划] 字符串 之 知识点汇总

学习一个算法, 需要弄清一些地方 ① 问题与算法的概念 ② 算法以及其思维轨迹 ③ 实现以及其思维轨迹 ④ 复杂度分析 ⑤ 应用 KMP算法 字符串匹配与KMP算法 为了方便弄清问题, 应该从特例入手. 设 A = " ababababb " , B = " ababa " , 我们要研究下面三个递进层次的字符串匹配问题: ① 是否存在 A 的子串等于 B ② 有几个 A 的子串等于 B ③ A 的哪些位置的子串等于 B KMP算法可以直接在线性复杂度解决问题③,

[补档计划] 覆盖子串与循环节

覆盖子串与循环节的概念 首先还是给覆盖子串和循环节下个定义. 为了方便后面的描述, 我们定义布尔记号 $P_S^T$ , 表示 $T$ 是否为 $S$ 的前缀. 对于一个定义, 为了对它有足够充分的了解, 可以通过多种形式描述这个定义, 这里就再引用图像的方法吧. 再引入前缀记号和后缀记号 pre[i] = S[1 : i], suf[i] = S[1 : |S|]. 对于字符串 S , 若字符串 T , 满足 $P_S^T$ , 且 $\exists k,~P_{kT}^S$ , 则称 T 为

[补档计划] SAM

对于一个算法或者数据结构的学习, 我们首先要弄清它的概念, 然后理解它的构建, 进而是实现和复杂度分析, 最后考虑如何应用它. 现在学习的是 SAM, Suffix Automaton, 后缀自动机. 推荐陈立杰的冬令营讲稿. https://wenku.baidu.com/view/90f22eec551810a6f4248606.html 什么是自动机? 有限状态自动机的功能是识别字符串. 令一个自动机 $A$ , 若 $A$ 能识别字符串 $S$ , 则 $A(S) = True$ ; 若

[补档计划] 后缀数组

后缀数组的实现 对于一个字符串 S , 有后缀数组 sa[1..n] , 排名数组 rk[1..n], 和辅助数组 height[1..n]. sa[i]: 在 S 的后缀中, 排名第 i 的后缀为 S[sa[i]: n] . rk[i]: 在 S 的后缀中, S[i:n] 的排名. height[i]: S[sa[i-1]:n] 与 S[sa[i]:n] 的 LCP. 显然有 rk[sa[i]] = i, sa[rk[i]] = i. 使用倍增的方法快速求 sa[1..n] 和 rk[1..n

[补档计划] 类欧几里得算法

$$\begin{aligned} f(a, b, c, n) & = \sum_{i = 0}^n \lfloor \frac{ai + b}{c} \rfloor \\ & = \sum_{i = 0}^n \sum_{j = 0}^{m-1} [j < \lfloor \frac{ai + b}{c} \rfloor] \\ & = \sum_{i = 0}^n \sum_{j = 0}^{m-1} [j + 1 \le \lfloor \frac{ai + b}{c}

[补档计划] 概率论

4.1 事件与概率 在一个黑箱中, 放着 3 个红球和 1 个白球. 我们从箱中取出一个球, 再放回去, 反复进行若干次. 每一次的结果是不确定的, 但总体上拿到红球的次数与拿到白球的次数接近 3 : 1 . 我们发现, 这类现象很常见, 那么我们就要尝试把这类现象的特点进行概括, 命名, 然后研究它的性质, 进而应用它. 概括一下这种现象: 在个别实验中其结果呈现出不确定性, 而在大量重复实验中其结果又具有统计规律性. 为了简便地称呼这种现象, 我们要给它起名字, 称之为 "随机现象"

[补档计划] 树6 - 莫队算法

[CF633H] Fibonacci-ish II 题意 给定长度为 $N$ 个序列 $A = (a_1, a_2, ..., a_N)$ . $M$ 组询问 $(l, r)$ : 将 $a_l, a_{l+1}, ..., a_r$ 提取出来, 排序, 去重, 得到长度为 $K$ 的序列 $B = (b_1, b_2, ..., b_K)$ , 求 $\sum_{i = 1}^K f_ib_i$ . 其中 $f_i$ 为斐波那契数列的第 $i$ 项: $f_0 = f_1 = 1, f_i =

[补档计划] 树4 - 线段树

[CF787D] Legacy 题意 $N$ 个点, $M$ 条连边操作: $1~u~v~w~:~u\rightarrow v$ . $2~u~l~r~w~:~u\rightarrow [l, r]$ $3~u~l~r~w~:~[l, r]\rightarrow u$ . 给定点 $s$ , 求单源最短路. $N\le 10^5$ . 分析 考虑使用 线段树结构 优化 建图. 建立一棵线段树, 每个点表示一个区间. 拆点, 线段树的每个点拆成 入点 和 出点 . 出线段树 的儿子连父亲, 因为可

[补档计划] 树2 - 树上倍增

[SCOI2016] 萌萌哒 题意 求有多少个无前导零的 $N(N\le 10^5)$ 位数 $A$ , 满足 $M(M\le 10^5)$ 个限制条件 $L~R~X~Y$ : $A[L+i] = A[X+i]$ . 分析 区间的信息就先对 ST 表用并查集, 然后下传. 实现 #include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <cmat