Skip to the content.

String

前缀函数

//pi[i]表示使得s[0...k-1]==s[k...i]的最大的k
//最长回文前缀可以将s与s的反串用特殊字符相连,然后对新串求前缀函数,返回s[0, pi[n-1]]

int pi[maxn];

void prefixFunction(const string& s) {
    int n = s.size(), k = 0;
    for (int i = 1; i < n; i++) {
        while (k&& s[k] != s[i])k = pi[k - 1];
        if (s[k] == s[i])k++;
        pi[i] = k;
    }
}

KMP

int nxt[maxn];
vector<int>ans;

void GetNextVal(string&pat) {
    int plen = pat.size(), i = 0, j = -1;
    nxt[0] = -1;
    while (i < plen) {
        while (j != -1 && pat[i] != pat[j])j = nxt[j];
        nxt[++i] = ++j;
    }
}

void KMP(string&pat, string&src) {
    int i = 0, j = 0, slen = src.size(), plen = pat.size();
    GetNextVal(pat);
    while (i < slen) {
        while (j != -1 && src[i] != pat[j])j = nxt[j];
        i++, j++;
        if (j >= plen) {
            //根据题意进行魔改
            ans.push_back(i - j + 1);
            //可重叠
            j = nxt[j];
            //不可重叠
            //j = 0;
        }
    }
}

KMP求最小循环节

int n = strlen(str);
int len = n - nxt[n];
if (len != n && n % len == 0)puts("0");//是一个完整的有循环节的字符串
else printf("%d\n", len - (n - n / len * len));//需要补充几个字母后才能成为有循环节的字符串

后缀数组(倍增法,nlogn)+Height数组[1-n]

//sfx(i)表示以i开头的后缀,即字符串s[i, n],以下关于顺序的注释都指的是字典序,注意数组与字符串下标均从1开始
//sa[]存的是后缀的顺序,保证sfx(sa[i]) < sfx(sa[i+1]),举个栗子,sa[1]==5意味着所有后缀中字典序最小的是s[5, n]
//rk[]存的是后缀的排名,如果sfx(i) < sfx(j),则rk[i] < rk[j],举个栗子,rk[4]==1意味着s[4, n]是所有后缀中字典序最小的(排第1位)
//可见sa[]和rk[]是一组逆运算,即sa[rk[i]]==rk[sa[i]]==i,举个栗子,如果s的所有后缀中字典序最小的是s[8, n],那么sa[rk[8]]==8, rk[sa[1]]==1
//辣么我们的目的是什么?尽可能高效地构造sa[]和rk[],从而构造height[],利用后缀、后缀序和height数组的性质来解题
//倍增法,简单来讲就是以有序的两个长度为k的子串的rk为键排序可以得到长度为2k子串的rk,当k倍增到字符串总长时得到的rk[]就是最终rk[]

const int maxn = 1e6 + 10;

string s;
//SA()用
int sa[maxn], rk[maxn];
int rk2[maxn];//第二关键字
int tong[maxn];//基数排序辅助数组
//GetHeight()用,height[]见main函数内注释
int height[maxn];

void SA(int len) {
    int i, M = 127, N = len, cnt = 1;//M为字符集大小,根据题意进行修改
    for (i = 1; i <= N; i++) rk[i] = s[i], rk2[i] = i;
    //先按首字母进行第一次基数排序
    for (i = 0; i <= M; i++) tong[i] = 0;
    for (i = 1; i <= N; i++) tong[s[i]]++;
    for (i = 1; i <= M; i++) tong[i] += tong[i - 1];
    for (i = N; i >= 1; i--) sa[tong[s[i]]--] = i;
    //k为倍增长度
    for (int k = 1; cnt < N; M = cnt, k <<= 1) {
        cnt = 0;
        //第二关键字的顺序其实可以通过上一次的sa[]得到
        //i+k超出n的子串所对应的第二关键字为无穷小(即s[i+k, i+2k]为空串),放在rk2[]最前面
        for (i = 1; i <= k; i++) rk2[++cnt] = N - k + i;
        for (i = 1; i <= N; i++)if (sa[i] > k) rk2[++cnt] = sa[i] - k;
        //用一二关键字进行基数排序
        for (i = 0; i <= M; i++) tong[i] = 0;
        for (i = 1; i <= N; i++) tong[rk[i]]++;
        for (i = 1; i <= M; i++) tong[i] += tong[i - 1];
        for (i = N; i >= 1; i--) sa[tong[rk[rk2[i]]]--] = rk2[i];
        //更新rk的时候旧rk会丢,所以用rk2存一下
        memcpy(rk2, rk, sizeof(rk));
        //如果两个子串相同(即一二关键字都相同),那么两个子串的rk也要相同(三目运算符判的就是这事儿)
        for (i = 1, cnt = 0; i <= N; ++i)
            rk[sa[i]] = (rk2[sa[i]] == rk2[sa[i - 1]] && rk2[sa[i] + k] == rk2[sa[i - 1] + k]) ? cnt : ++cnt;
    }
}

void GetHeight(int n) {
    for (int i = 1, k = 0; i <= n; ++i) {
        if (k) --k;
        while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
        height[rk[i]] = k;
    }
}

int main() {
    cin >> s;
    int len = s.size();
    s = "#" + s;
    SA(len);
    GetHeight(len);
    //height[i]=sfx(sa[i-1])和sfx(sa[i])的最长公共前缀的长度,也就是排名相邻两个后缀的最长公共前缀的长度。
    //询问某两个后缀的最长公共前缀可以转化为求height数组区间最小值(可以用RMQ)
    //ans为本质不同的子串个数法一
    LL ans = (len + 1)*len / 2;
    for (int i = 1; i <= len; i++)ans -= height[i];
    //本质不同的子串个数法二
    LL ans = 0;
    for (int i = 1; i <= len; i++)ans += (len - sa[i] + 1 - height[i]);
    //可重叠最长重复子串,将原串s改写为s + "#" + reverse(s)可求得最长回文子串(But why not use manacher directly?)
    max(height);
    cout << ans << endl;
    return 0;
}

后缀数组(倍增法,nlog^2(n))

//一个比较好背且好理解的版本,复杂度略高,参数和变量见上
void SA(int n) {
    int i, cnt;
    for (i = 1; i <= n; ++i) rk[i] = s[i];
    for (int k = 1; k < n; k <<= 1) {
        for (i = 1; i <= n; ++i) sa[i] = i;
        sort(sa + 1, sa + n + 1, [k](int x, int y) {return rk[x] == rk[y] ? rk[x + k] < rk[y + k] : rk[x] < rk[y]; });
        memcpy(rk2, rk, sizeof(rk));
        for (i = 1, cnt = 0; i <= n; ++i)
            rk[sa[i]] = (rk2[sa[i]] == rk2[sa[i - 1]] && rk2[sa[i] + k] == rk2[sa[i - 1] + k]) ? cnt : ++cnt;
    }
}

后缀数组求不可重叠最长重复子串

//利用height数组分组
bool judge(int k, int len) {
    int Max = sa[1], Min = sa[1];
    for (int i = 2; i <= len; i++) {
        if (height[i] >= k)Max = max(Max, sa[i]), Min = min(Min, sa[i]);
        else Max = sa[i], Min = sa[i];
        if (Max - Min >= k)return true;
    }
    return false;
}

//main函数(假设sa数组和height数组已求出)
//二分长度(即二分答案)
int L = 0, R = n, ans = 0;
while (L <= R) {
    int mid = (L + R) / 2;
    if (judge(mid, n))L = mid + 1, ans = max(ans, mid);
    else R = mid - 1;
}
if (ans < 4)puts("0");
else printf("%d\n", ans + 1);

后缀数组求可重叠出现k次的最长重复子串

bool judge(int mid, int len, int k) {
    int cnt = 1;
    for (int i = 1; i <= len; i++) {
        if (height[i] >= mid)cnt++;
        else cnt = 1;
        if (cnt >= k)return true;
    }
    return false;
}
//同上二分长度

后缀自动机

const int maxn = 3 * 1e6 + 10;

char s[maxn];
int siz[maxn];
LL ans = 0;

struct SuffixAutoMaton {
    int last, cnt;
    int trans[maxn << 1][26], fa[maxn << 1], len[maxn << 1];
    SuffixAutoMaton() { last = cnt = 1; }//以1为根,cnt记录节点个数
    void insert(int c) {
        int p = last, np = ++cnt;//p为以c结尾的前缀的倒数第二个节点,np为倒数第一个(新建)
        last = np;//更新last
        len[np] = len[p] + 1;
        siz[np] = 1;//siz[i]表示i节点所代表的endpos的集合元素大小,
        //即所对应的字符串集的longest出现的次数(之后用的时候可能需要一个累加)
        while (p && !trans[p][c]) { trans[p][c] = np; p = fa[p]; }//沿着parent树跳!(边跳边更新)
        //case1:如果跳到最前面的根(S),把np的parent树上的father置为1(即根)
        if (!p)fa[np] = 1;
        else {
            int q = trans[p][c];//q表示从np一直跳parent树所得到的 第一个 有c儿子的节点 的c儿子
            //case2:如果节点q表示的endpos所对应的字符串集合只有一个字符串,那么把np的parent树上的父亲设置为q
            if (len[p] + 1 == len[q])fa[np] = q;
            else {
                //case3:复制q点
                //同时len要更新(注意len[q] != len[p] + 1, 因为加点会使x父亲改变)
                int nq = ++cnt;//新建复制点nq
                fa[nq] = fa[q];//复制q点的父亲
                memcpy(trans[nq], trans[q], sizeof(trans[q]));//复制q点的儿子
                len[nq] = len[p] + 1;//更新len
                fa[q] = fa[np] = nq;//把q点和np点的father指向复制点nq
                while (p&&trans[p][c] == q) { trans[p][c] = nq; p = fa[p]; }//将前面所有连q的点连向nq
            }
        }
    }
} sam;

int main() {
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    for (int i = 1; i <= len; i++) sam.insert(s[i] - 'a');
    return 0;
}

后缀自动机求本质不同的子串数目

LL ans = 0;
for (int i = 1; i <= sam.cnt; i++)
    ans += sam.len[i] - sam.len[sam.fa[i]];
cout << ans << endl;

回文树

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

const int N=2e5+5;
typedef long long ll;

char s[N];
int S[N],len[N],fail[N],ch[N][26],tot,n;
ll cnt[N];

int new_node(int x)
{
    len[tot]=x,cnt[tot]=0;
    memset(ch[tot],0,sizeof(ch[tot]));
    return tot++;
}

int get_fail(int x)
{
    while(S[n-len[x]-1]!=S[n]) x=fail[x];
    return x;
}

void add(char *s)
{
    tot=0,new_node(0),new_node(-1);
    fail[0]=1,fail[1]=1,S[0]=-1,n=0;
    int last=0;
    for(int i=0;s[i];i++)
    {
        int c=s[i]-'a';
        S[++n]=c;
        int cur=get_fail(last);
        if(!ch[cur][c])
        {
            int now=new_node(len[cur]+2);
            fail[now]=ch[get_fail(fail[cur])][c];
            ch[cur][c]=now;
        }
        last=ch[cur][c];
        cnt[last]++;
    }
}

ll query(char *s)
{
    S[0]=-1,n=0;
    int last=0;ll res=0;
    for(int i=0;s[i];i++)
    {
        int c=s[i]-'a';
        S[++n]=c;
        int cur=last;
        while(cur!=1&&(!ch[cur][c]||S[n-len[cur]-1]!=S[n]))
            cur=fail[cur];
        last=ch[cur][c];
        res+=cnt[last];
    }
    return res;
}

void solve(int cas)
{
    scanf("%s",s);
    add(s);
    for(int i=tot-1;i>=0;i--) cnt[fail[i]]+=cnt[i];
    cnt[0]=cnt[1]=0;
    for(int i=2;i<=tot-1;i++) cnt[i]+=cnt[fail[i]];
    scanf("%s",s);
    ll ans=query(s);
    printf("Case #%d: %lld\n",cas,ans);
}

int main()
{
    //freopen("G.in","r",stdin);
    int T,cas=0;
    scanf("%d",&T);
    while(T--)
        solve(++cas);
    return 0;
}

s中选两个非空子串a,b(可重叠),使它们拼起来是一个回文串,问选择方案数

const int maxn = 2e5 + 10;
int n, pos, d[maxn * 2];
char s[maxn * 2], S[maxn * 2];
LL ans, sum;

template <class T>
void print(T a)
{
    if (a > 9) print(a / 10);
    putchar(a % 10 + '0');
}

struct Palindrome_tree {
    int N, last;
    struct T {
        int Next[30], fail, cnt, num, len;
    } tree[maxn];
    void init()
    {
        for (int i = 0; i <= N; i++) {
            for (int j = 0; j < 26; j++) tree[i].Next[j] = 0;
            tree[i].fail = tree[i].cnt = tree[i].num = tree[i].len = 0;
        }
        N = 1, tree[N].len = -1, tree[N].fail = 1;
        N++, tree[N].len = 0, tree[N].fail = 1;
        last = 2;
    }

    int get_fail(int x)
    {
        while (s[pos - 1 - tree[x].len] != s[pos]) x = tree[x].fail;
        return x;
    }

    void Add(int c)
    {
        int Now = get_fail(last);
        if (!tree[Now].Next[c]) {
            last = ++N;
            tree[N].len = tree[Now].len + 2;
            int f = get_fail(tree[Now].fail);
            tree[N].fail = tree[f].Next[c] ? tree[f].Next[c] : 2;
            tree[N].num = tree[tree[N].fail].num + 1;
            tree[Now].Next[c] = N;
        }
        else
            last = tree[Now].Next[c];
        tree[last].cnt++;
    }
} palindrome_tree;

struct SAM {
    int mm, son[maxn * 4][30], pre[maxn * 4], step[maxn * 4], last, total, root,
        p[maxn * 4], q[maxn * 4], h[maxn * 4];
    LL f[maxn * 4], g[maxn * 4];
    void init()
    {
        for (int i = 0; i <= total; i++) {
            for (int j = 0; j < 30; j++) son[i][j] = 0;
            pre[i] = step[i] = f[i] = g[i] = p[i] = q[i] = 0;
        }
        total = last = root = 1;
    }
    struct node {
        int node, Next;
    } G[maxn * 4];
    void Insert(int a, int b)
    {
        mm++;
        G[mm].node = b;
        G[mm].Next = h[a];
        h[a] = mm;
    }
    inline void Push(int v) { step[++total] = v; }
    void Extend(int ch)
    {
        Push(step[last] + 1);
        int p = last, np = total;
        for (; !son[p][ch]; p = pre[p]) son[p][ch] = np;
        if (!p)
            pre[np] = root;
        else {
            int q = son[p][ch];
            if (step[p] + 1 != step[q]) {
                Push(step[p] + 1);
                int nq = total;
                memcpy(son[nq], son[q], sizeof(son[q]));
                pre[nq] = pre[q];
                pre[q] = pre[np] = nq;
                for (; son[p][ch] == q; p = pre[p]) son[p][ch] = nq;
            }
            else
                pre[np] = q;
        }
        last = np;
    }
    void dfs1(int u, int fa)
    {
        for (int Now = h[u]; Now != -1; Now = G[Now].Next) {
            int v = G[Now].node;
            if (v == fa) continue;
            dfs1(v, u);
            f[u] += f[v];
        }
        //        printf("fff%d %lld\n",u,f[u]);
    }
    void dfs2(int u, int fa)
    {
        g[u] = f[u] * (step[u] - step[fa]);
        g[u] += g[fa];
        for (int Now = h[u]; Now != -1; Now = G[Now].Next) {
            int v = G[Now].node;
            if (v == fa) continue;
            dfs2(v, u);
        }
        if (q[u] >= 1 && q[u] <= n) {
            ans += g[u] * d[q[u] + 1];
            sum += g[u];
            //            printf("aaa%d %lld\n",u,g[u]);
        }
    }
    void Build()
    {
        init();
        for (int i = 1; i <= 2 * n + 1; i++) Extend(s[i] - 'a');
        int tmp = 1;
        for (int i = 1; i <= 2 * n + 1; i++) {
            tmp = son[tmp][s[i] - 'a'];
            p[i] = tmp;
            q[tmp] = i;
            if (i > n + 1) f[tmp] = 1;
        }
        mm = -1;
        for (int i = 0; i <= total; i++) h[i] = -1;
        for (int i = 2; i <= total; i++) Insert(pre[i], i);
        dfs1(root, root);
        dfs2(root, root);
    }
} sam;

int main()
{
    scanf("%d", &n);
    scanf("%s", S + 1);
    s[0] = '#';
    for (int i = 1; i <= n; i++) s[i] = S[n + 1 - i];
    palindrome_tree.init();
    for (int i = 1; i <= n; i++) {
        pos = i, palindrome_tree.Add(s[i] - 'a');
        d[n + 1 - i] = palindrome_tree.tree[palindrome_tree.last].num;
    }
    //    for (int i=1; i<=n; i++) printf("%d ",d[i]); printf("\n");
    for (int i = 1; i <= n; i++) s[i] = S[i];
    s[n + 1] = 'a' + 27;
    for (int i = 1; i <= n; i++) s[i + n + 1] = S[n + 1 - i];
    sam.Build();
    //    printf("%lld %lld\n",ans,sum);

    for (int l = 1, r = n; l < r; l++, r--) swap(S[l], S[r]);
    for (int i = 1; i <= n; i++) s[i] = S[n + 1 - i];
    palindrome_tree.init();
    for (int i = 1; i <= n; i++) {
        pos = i, palindrome_tree.Add(s[i] - 'a');
        d[n + 1 - i] = palindrome_tree.tree[palindrome_tree.last].num;
    }
    //    for (int i=1; i<=n; i++) printf("%d ",d[i]); printf("\n");
    for (int i = 1; i <= n; i++) s[i] = S[i];
    s[n + 1] = 'a' + 27;
    for (int i = 1; i <= n; i++) s[i + n + 1] = S[n + 1 - i];
    sam.Build();
    //    printf("%lld %lld\n",ans,sum);
    ans = ans + sum / 2;
    print(ans);
    putchar(10);
    return 0;
}

Trie树

#define R 52//字符集大小
class TrieNode {
public:
    int val, sons_cnt;
    bool exist;
    TrieNode*nxt[R];
    TrieNode() :val(0), sons_cnt(0), exist(false) {
        for (int i = 0; i < R; i++)nxt[i] = nullptr;
    };
};

class Trie {
public:
    TrieNode*root;
    Trie() { root = new TrieNode(); }
    void insert(string&s, int v) {
        insert(s, root, 0, v, nullptr);
    }
    void insert(string&s) {
        insert(s, root, 0, nullptr);
    }
    string find_longest_common_prefix(TrieNode*node) {
        if (node == nullptr || (node != nullptr&&node->sons_cnt > 1))return "";
        int i = 0;
        for (i = 0; i < R; i++) {
            if (node->nxt[i] != nullptr)break;
        }
        if (i == R)return "";
        char c = ((i > 25) ? ('A' + i) : ('a' + i));
        string tmp;
        tmp.insert(tmp.begin(), c);
        if (node->val)return "";
        else return tmp + find_longest_common_prefix(node->nxt[i]);
    }
private:
    TrieNode* insert(string&s, TrieNode*node, int loc, int v, TrieNode*father) {
        int len = s.length();
        if (len == 0) {
            node->exist = true, node->val = v, node->sons_cnt++;
            return node;
        }
        if (node == nullptr) {
            node = new TrieNode();
            if (father != nullptr)father->sons_cnt++;
        }
        if (len == loc) { node->exist = true, node->val = v; return node; }
        if (s[loc] >= 'a')node->nxt[s[loc] - 'a'] = insert(s, node->nxt[s[loc] - 'a'], loc + 1, v, node);
        else node->nxt[s[loc] - 'A' + 26] = insert(s, node->nxt[s[loc] - 'A' + 26], loc + 1, v, node);
        return node;
    }
    TrieNode* insert(string&s, TrieNode*node, int loc, TrieNode*father) {
        int len = s.length();
        if (len == 0) {
            node->exist = true, node->sons_cnt++;
            return node;
        }
        if (node == nullptr) {
            node = new TrieNode();
            if (father != nullptr)father->sons_cnt++;
        }
        if (len == loc) { node->exist = true; return node; }
        if (s[loc] >= 'a')node->nxt[s[loc] - 'a'] = insert(s, node->nxt[s[loc] - 'a'], loc + 1, node);
        else node->nxt[s[loc] - 'A' + 26] = insert(s, node->nxt[s[loc] - 'A' + 26], loc + 1, node);
        return node;
    }
};


int main() {
    Trie a;
    vector<string>strs = { "she","shell","sells","sea","shore","s" };
    for (auto s : strs)a.insert(s);
    cout << a.find_longest_common_prefix(a.root) << endl;
    return 0;
}

Manacher和最长回文子串

char ma[maxn];
int mp[maxn];
void manacher(string &s) {
    int l = 0;
    ma[l++] = '$', ma[l++] = '#';
    for (int i = 0; i < s.size(); i++)ma[l++] = s[i], ma[l++] = '#';
    ma[l] = 0;
    int mx = 0, id = 0;
    for (int i = 0; i < l; i++) {
        mp[i] = mx > i ? min(mp[2 * id - i], mx - i) : 1;
        while (ma[i + mp[i]] == ma[i - mp[i] >= 0 ? i - mp[i] : 0])mp[i]++;
        if (i + mp[i] > mx)mx = i + mp[i], id = i;
    }
}
string longestPalindrome(string s) {
    manacher(s);
    int max_len = 0, max_pos;
    for (int i = 0; i < maxn; i++)
        if (mp[i] > max_len)max_len = mp[i], max_pos = i;
    max_len--;
    return s.substr((max_pos - max_len) / 2, max_len);
}