字符串板子

只是板子和性质, 主要用于复习, 想要学习的话, 去找其他帖子
以前都是懂原理的, 好久不写就忘的差不多了, 这东西会用就好, 懂原理更好

kmp及其扩展

kmp

f[i]表示 模板串t的以第i个字符结尾的后缀字符串 与 模板串前缀字符串 匹配的最大长度

n-f[n] 为字符串的最小循环节长度(忽略最后没显示的) ababa 5-3=2,故循环节(ab)长度为2

aaabaa 最小循环节为aaab,但还有aaaba、aaabaa

对于完美覆盖,最小循环节完美覆盖字符串,则其他循环节必定是最短循环节的倍数

kmp扩展

extend[i]表示 匹配串s的以第i个字符结尾的后缀字符串 与 模板串t前缀字符串 匹配的最大长度

一般用于查找匹配串中有多少个模板串

代码

#include<bits/stdc++.h>
using namespace std;
const int LenT = 1e5 + 5, LenS = 1e6 + 5;
char t[LenT], s[LenS];
int lens, lent;
int f[LenT], extend[LenS];
int cnt;
//n-f[n] 为字符串的最小循环节长度(忽略最后没显示的) ababa 5-3=2,故循环节(ab)长度为2
//aaabaa 最小循环节为aaab,但还有aaaba、aaabaa
//对于完美覆盖,最小循环节完美覆盖字符串,则其他循环节必定是最短循环节的倍数
void KMP()
{
    f[1] = 0;
    for (int i = 2, j = 0; i <= lent; ++i)
    {
        while (j > 0 && t[i] != t[j + 1]) j = f[j];
        if (t[i] == t[j + 1]) ++j;
        f[i] = j;
    }
}

void ext_KMP()
{
    extend[1] = 0;
    for (int i = 1, j = 0; i <= lens; ++i)
    {
        while (j > 0 && (j == lent || s[i] != t[j + 1]))
            j = f[j];
        if (s[i] == t[j + 1])
            ++j;
        extend[i] = j;
        if (j == lent)
            ++cnt;
    }
}

int main()
{
    scanf("%s%s", t+1, s+1);
    lens = strlen(s+1);
    lent = strlen(t+1);
    KMP();
    ext_KMP();
    printf("%d", cnt);
    return 0;
}

Trie数

就是简单的把给的所有串全部放到树中, 但是空间开销较大, 为 (字符种类数^{maxlen}) 或者 maxlen * 字符种类数

通常用于寻找匹配串中有多少模板串

代码

#include<bits/stdc++.h>
using namespace std;
const int SIZE=100+5;
int trie[SIZE][26],tot=1,ended[SIZE];
void insert(char *str)
{
    int len=strlen(str),p=1;
    for(int k=0;k<len;++k)
    {
        int ch=str[k]-'a';
        if(trie[p][ch]==0)
            trie[p][ch]=++tot;
        p=trie[p][ch];
    }
    ended[p]=true;
}
bool search(char *str)
{
    int len=strlen(str),p=1;
    for(int k=0;k<len;++k)
    {
        p=trie[p][str[k]-'a'];
        if(p==0)
            return 0; 
    }
    //如果end[p]=false,则相当于有 cat 查 ca 尽管存在 ca 单词 但并没有 ca
    return ended[p];
}
int main()
{
    return 0;
} 

马拉车

寻找最大回文串

核心思想
字符串板子

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 110005;

int pArr[N << 1];
char s[N], chaArr[N << 1];

int maxLcsplength() {
    register int len = 0;
    chaArr[len++] = '$', chaArr[len++] = '#';

    for (register int i = 0; s[i]; ++i) chaArr[len++] = s[i], chaArr[len++] = '#';
    chaArr[len] = '';

    register int R = -1, C = -1, maxN = 0;
    for (register int i = 0; i < len; ++i) {
        pArr[i] = R > i ? min(R - i, pArr[(C << 1) - i]) : 1;
        while (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) ++pArr[i];
        if (i + pArr[i] > R) R = i + pArr[i], C = i;
        maxN = max(maxN, pArr[i]);
    }
    return maxN - 1;
}

int main() {
    while (gets(s)) {
        printf("%dn", maxLcsplength());
        getchar();
    }
    return 0;
}

字符串环的最大最小表示

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 10000;

char str[N << 1];
int len;

int getMin(char s[]) {
    int i = 0, j = 1, k = 0, t;
    while (i < len && j < len && k < len) {
        t = s[(i + k) % len] - s[(j + k) % len];
        if (t == 0) ++k;
        else {
            if (t > 0) i += k + 1;
            else j += k + 1;
            if (i == j) ++j;
            k = 0;
        }
    }
    return i > j ? j : i;
}

int getMax(char s[]) {
    int i = 0, j = 1, k = 0, t;
    while (i < len && j < len && k < len) {
        t = s[(i + k) % len] - s[(j + k) % len];
        if (t == 0) ++k;
        else {
            if (t < 0) i += k + 1;
            else j += k + 1;
            if (i == j) ++j;
            k = 0;
        }
    }
    return i > j ? j : i;
}

int main()
{
    scanf("%s", str + 1);
    len = strlen(str + 1);
    strncpy(str + len + 1, str + 1, len-1);

    char maxa[N], mina[N];
    strncpy(maxa + 1, str + getMax(str), len);
    strncpy(mina + 1, str + getMin(str), len);

    printf("%sn%s", maxa + 1, mina + 1);
    return 0;
}

AC自动机

Trie树的思想, 就是多了个失配指针

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

#define RE register
#define FOR(i,a,b) for(RE int i=a;i<=b;++i)
#define ROF(i,a,b) for(int i=a;i>=b;--i)
#define ll long long 
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define ss(s) scanf("%s",s)

using namespace std;

const int MAXN = 1000005;
const int MAXM = 51;
const int NUM = 10001;
const int C = 'a';
const int M = 26;

struct AC {
    static const int N = 1e5 + 5, M = 26, C = 'a'; //字符串总长度, 字符范围

    int trie[N][M], cnt[N], fail[N];
    vector<VI> idx; //记录节点结尾的字符串id
    int q[N], tot;

    void init() {
        rep (i, 0, M - 1) trie[0][i] = 0;
        tot = 0; 
        idx.resize(1, VI());
    }

    int newnode() {
        cnt[++tot] = 0; fail[tot] = 0;
        rep (i, 0, M - 1) trie[tot][i] = 0;
        idx.pb(VI());
        return tot;
    }

    void insert(char* s, int id) {
        int p = 0;
        for (int i = 0; s[i]; ++i) {
            int ch = s[i] - C;
            if (!trie[p][ch]) trie[p][ch] = newnode();
            p = trie[p][ch];
        }
        ++cnt[p];
        idx[p].pb(id);
    }

    void build() {
        int head = 0, tail = -1;
        rep (i, 0, M - 1) if (trie[0][i]) q[++tail] = trie[0][i];
        while (head <= tail) {
            int p = q[head++];
            rep (i, 0, M - 1)
                if (trie[p][i])
                    fail[trie[p][i]] = trie[fail[p]][i], q[++tail] = trie[p][i];
                else trie[p][i] = trie[fail[p]][i];
        }
    }

    int query(char* s) {
        set<int> vis;
        int p = 0, res = 0;
        for (int i = 0; s[i]; ++i) {
            p = trie[p][s[i] - C];
            for (int tmp = p; tmp && !vis.count(tmp); tmp = fail[tmp])
                res += cnt[tmp], vis.insert(tmp);
        }
        return res;
    }
} a;

int _, n;
char s[MAXN];

int main() {
    for (sc(_); _; --_) {
        sc(n);
        FOR(i, 1, n)
            ss(s), a.insert(s, i);
        a.build();
        ss(s);
        printf("%dn", a.query(s));
        a.init();
    }
    return 0;
}

后缀数组(nlogn)

太菜了, 只会倍增的写法

sa和rk(rank)数组互逆, sa[i]表示以i为开头的原串的后缀串的排名, rk[i]表示原串后缀串第i名在原串中的位置

后缀数组sa,rk有了没啥用, 主要是得到 heigh数组, hi表示 rk[i] 和 rk[i - 1]的后缀串的最大前缀长度

然后就是倍增rmq

倍增rmq

有的题直接给你一堆字符串, 可以直接sort(), 然后预处理, 按照后缀数组的rmq去写

代码

#include <bits/stdc++.h>
#define RE register
#define FOR(i,a,b) for(RE int i=a;i<=b;++i)
#define ROF(i,a,b) for(RE int i=a;i>=b;--i)
using namespace std;
const int N = 30000 + 9;
char str[N];
int sa[N], rk[N], tp[N], tax[N], lcp[N], len, f[N][30], M;

inline void sort() {
    memset(tax, 0, (M + 1) * sizeof(int));
    //FOR(i, 0, M)tax[i] = 0;
    FOR(i, 1, len) ++tax[rk[i]];
    FOR(i, 1, M) tax[i] += tax[i - 1];
    ROF(i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
}

void getH() {
    int j, k = 0;
    FOR(i, 1, len) {
        if (k) --k;
        j = sa[rk[i] - 1];
        while (str[i + k] == str[j + k])++k;
        lcp[rk[i]] = k;
    }
}


void SuffixSort() {
    M = 200;
    FOR(i, 1, len) rk[i] = str[i], tp[i] = i;
    sort();
    for (RE int w = 1, p = 0; p < len; w <<= 1, M = p) {
        p = 0;
        FOR(i, 1, w)tp[++p] = len - w + i;
        FOR(i, 1, len)if (sa[i] > w)tp[++p] = sa[i] - w;
        sort();
        swap(tp, rk);
        rk[sa[1]] = p = 1;
        FOR(i, 2, len)
            rk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]] 
                && tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
    }
    getH();
}

void rmq_init() {
    memset(f, 63, sizeof f);
    FOR(i, 0, len-1)f[i][0] = lcp[i + 1];
    for (int j = 1,mj=2; mj <= len; ++j,mj<<=1)
            for (int i = 1; i + mj <= len; ++i)
                f[i][j] = min(f[i][j - 1], f[i + (mj>>1)][j - 1]);
}

int rmq_query(int l, int r) {
    if (l<1 || r>len)return 0;
    l = rk[l], r = rk[r];
    if (l > r)swap(l, r);
    int k =r-l<2?0:ceil(log2(r-l))-1; 
    return min(f[l][k], f[r - (1 << k)][k]);
}

int main() {
    scanf("%s", str + 1);
    len = strlen(str + 1);
    SuffixSort();
    getH();
    FOR(i, 1, len)
        printf("%d ", sa[i]-1);
    puts("");
    FOR(i,1,len)
        printf("%d ",lcp[i]);
    return 0;
}

后缀自动机

仿着AC自动机有个失配指针, 不过是endpos等价类的父类

这东西是工具, 要会树上dp才行

//1.如果两个子串�? endpos 相同,则其中子串一个必然为另一个的后缀

//2.对于任意两个子串 t �? p (len_t<=len_p)要么 endpos(p)∈endpos(t) ,要�? endpos(t)∩endpos(p)= �?

//3.对于 endposendpos 相同的子串,我们将它们归为一�? endposendpos 等价类。对于任意一�? endposendpos
//等价类,将包含在其中的所有子串依长度从大到小排序,则每一个子串的长度均为上一个子串的长度�? 11 ,且�?
//上一个子串的后缀(简单来说,一�? endposendpos 等价类内的串的长度连续)

//4.endpos 等价类个数的级别�? O(n)

//5.一个类 aa 中,有最长的子串,也有最短的子串,我们称最长子串的长度�? len(a)len(a) ,最短子�?
//长度�? minlen(a) 。对于存在父子关系的两个类,�? fa(a) 表示�? a 的父亲(也是一个类)。则�? l
//en(fa(a))+1=minlen(a)

//6.后缀自动机的边数�? O(n)O(n)

/////////////////后缀自动机的一般性可以让我们处理的问题有:判断子串,计算不同子串个数等�?

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 4e5 + 10;

struct node
{
    int fa, len;
    map<char, int> nxt;
}tr[N];

int sz, las;

void init()
{
    sz = las = 1;
    tr[1].len = tr[1].fa = 0;
    map<char, int>().swap(tr[1].nxt);
}

void add(char c)
{
    int p = las; int cur = las = ++sz;
    tr[cur].len = tr[p].len + 1;
    for (; p && !tr[p].nxt.count(c); p = tr[p].fa)
        tr[p].nxt[c] = cur;
    if (p == 0) { tr[cur].fa = 1; return; }
    int q = tr[p].nxt[c];
    if (tr[q].len == tr[p].len + 1) { tr[cur].fa = q; return; }
    int nq = ++sz; tr[nq] = tr[q];
    tr[nq].len = tr[p].len + 1;
    for (; p && tr[p].nxt[c] == q; p = tr[p].fa)
        tr[p].nxt[c] = nq;
    tr[q].fa = tr[cur].fa = nq;
}

int dp[N], n;
char s[N >> 1];

void solve()
{
    int lenx = 1, p = 1, tmp =  0;
    for(int& i = lenx; s[i]; ++i)
    {
        if (tr[p].nxt.count(s[i])) p = tr[p].nxt[s[i]], ++tmp; 
        else 
        {
            while (!tr[p].nxt.count(s[i]) && p) p = tr[p].fa;
            if(p == 0) p = 1, tmp = 0;
            else tmp = tr[p].len + 1, p = tr[p].nxt[s[i]]; //注意顺序
        }
        if(!tmp) { puts("-1"); return; }
        dp[i] = dp[i - tmp] + 1;
    }--lenx;
    printf("%dn", dp[lenx]);
}

int main()
{
    scanf("%s%d", s + 1, &n); init();
    for (int i = 1; s[i]; ++i) add(s[i]);
    for (int i = 1; i <= n; ++i)
        scanf("%s", s + 1), solve();
    return 0;
}

原文链接: https://www.cnblogs.com/2aptx4869/p/13154795.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    字符串板子

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/356391

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 上午11:11
下一篇 2023年3月2日 上午11:11

相关推荐