[补档计划] 字符串

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[补档计划] 字符串相关的知识,希望对你有一定的参考价值。

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

    ① 问题与算法的概念;

    ② 算法, 思维轨迹

    ③ 实现, 思维轨迹;

    ④ 复杂度分析;

    ⑤ 应用.

 

 

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

 



以上是关于[补档计划] 字符串的主要内容,如果未能解决你的问题,请参考以下文章

[补档计划] SAM

[补档计划] 后缀数组

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

[补档计划] 数位dp

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

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