SA复习笔记

(背的 S A SA SA是从 0 0 0 n n n的)
几个数组的意义:

s a [ i ] sa[i] sa[i]表示排名为 i i i的后缀的首字母位置。

t [ i ] t[i] t[i]表示后缀首字母在 i i i这个位置的第一关键字的排名。

t 2 [ i ] t2[i] t2[i]表示第二关键字排名为 i i i的后缀的首字母位置。

h e i g h t [ i ] height[i] height[i] 表示排名为 i i i i − 1 i-1 i1的两个后缀的 L C P LCP LCP

板子标准代码:

inline void build_sa(int m) {
    for (int i = 0; i < m; ++i) c[i] = 0;
    for (int i = 0; i < n; ++i) c[t[i] = s[i]]++;
    for (int i = 1; i < m; ++i) c[i] += c[i - 1];
    for (int i = n - 1; i >= 0; --i) sa[--c[t[i]]] = i;
    for (int k = 1; k <= n; k <<= 1) {
        int p = 0;
        for (int i = n - k; i < n; ++i) t2[p++] = i;
        for (int i = 0; i < n; ++i) if (sa[i] >= k) t2[p++] = sa[i] - k;
        for (int i = 0; i < m; ++i) c[i] = 0;
        for (int i = 0; i < n; ++i) c[t[t2[i]]]++;
        for (int i = 1; i < m; ++i) c[i] += c[i - 1];
        for (int i = n - 1; i >= 0; --i) sa[--c[t[t2[i]]]] = t2[i];
        swap(t, t2);
        p = 1; t[sa[0]] = 0;
        for (int i = 1; i < n; ++i) {
            t[sa[i]] = ((t2[sa[i - 1]] == t2[sa[i]] && (sa[i - 1] + k < n ? t2[sa[i - 1] + k] : -1) == (sa[i] + k < n ? t2[sa[i] + k] : -1)) ? (p - 1) : p++);
        }
        if (p >= n) break;
        m = p;
    }
}
inline void build_hei() {
    int j, k = 0;
    for (int i = 0; i < n; ++i) rnk[sa[i]] = i;
    for (int i = 0; i < n; ++i) {
    	if (!rnk[i]) continue;
        if (k) k--;
        j = sa[rnk[i] - 1];
        while (s[j + k] == s[i + k]) k++;
        hei[rnk[i]] = k;
    }
}
inline void predo() {
    for (int i = 0; i < n; ++i) f[i][0] = hei[i];
    for (int j = 1; (1 << j) <= n; ++j) {
        for (int i = 0; i + (1 << j) - 1 < n; ++i) {
            f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
}
inline int LCP(int l, int r) {
    int a = rnk[l], b = rnk[r];
    if (a > b) swap(a, b); a++;
    int k = (int)log2(b - a + 1);
    return min(f[a][k], f[b - (1 << k) + 1][k]);
}
一些套路

一:后缀的LCP就是两个完全相等的子串
二:求最长可重叠重复子串——求height的最大值
三:求最长不可重叠重复子串——二分长度k,因为LCP是区间height的RMQ。所以扫一遍,按照区间分组。一旦有一个height<k了,说明这一组不能要。就在这一组处断开。对于每一组,求一求里面SA的最大值和最小值。可以在扫一遍的时候完成。
四:求最长可重叠k重复子串——二分长度t。同样分成若干组,考察每一组的大小是否有大于等于k的就可以了。
五:求不相同的子串的个数——按照排名降低的顺序处理。即(sa[1],sa[2],sa[3])的顺序处理。可以发现每加入一个后缀,它会产生n-sa[i]+1个前缀。但是其中有height[i]个是相同的。所以此次对答案的贡献就是n-sa[i]+1-height[i]。所有加起来就好了。
六:最长回文子串。 ①manacher。 ②把原来的串翻转接到原串后面(注意中间用一个特殊字符隔开)。枚举原来的每一个中心位置。就是求LCP的最大值了。实现起来有点细节。
七:给一个多次重复而来的一个串,求(不可重叠)最多重复次数。 枚举重复长度k,先判断能否整除。如果可以整除的话再判断LCP(0,k)是不是n-k。由于0是固定的。所以预处理的时候只需要预处理每个数到rnk[0]+1这个位置的最小值就可以了。可以从rnk[0]+1这个位置向外扫。
*八:对于随意一个串(不保证是重复而来),求(可重叠)最多重复次数。 枚举长度L。考虑答案一定有2个对应的位置(距离为L,字符相等)。枚举这N/L个可能位置,与它前面的那个位置形成一个可能对。分别向前向后求LCP。假设长度为k,那么此时的长度为K/L+1. 说明:这个东西的重复性比较显然,LCP就代表了重复性。连续性可以考虑有相交。(不是特别理解)
九:最长公共子串。两个字符串连接起来,中间用一个不可能出现的字符连接。仿照第二问的思路,不过要特殊要求两个不在同一边。这个查询一下sa就好了。
十:给两个串,求长度不小于k的公共子串的个数。 先连起来。考虑如果没有长度的限制且没有必须在两边的限制,就是求所有的LCP的和。可以使用单调栈O(n)维护出来。(后面有这样的题) 有长度的限制的话,按照height分组。每一组分别做。最后再加起来就好了。有两边的限制的话,一种方法是容斥,求三个SA。另外一个方法是就考虑加入每个B的后缀,对A有什么贡献。用单调栈这样维护A。然后再反过来维护B就好了。具体操作起来(以加入B为例),按照rnk的顺序枚举,sa可以指引当前这个是B的还是A的。如果是A的就插到栈里面,如果是B的就弹出那些大于B的元素。统计一下答案。(注意这里统计要记录B的下标差,不能A,B的下标混着统计)
十一:在至少 k k k个字符串中出现过的最长子串。 把这若干个字符窜用不同的没有出现过的字符连接起来,求一个SA。二分长度,按照height的rnk顺序分组。观察他们的SA定位的位置是否在至少k个区间里。
十二:求每个字符串至少出现两次且不重叠的最长子串 。连起来,求SA,二分长度分组。看每组的是否存在两个相同的height且在原来的同一字符串中且他们对应的SA对应回去是距离大于长度的。扫的时候用一个桶记录。
十三:求 出现或反转后出现在每个字符串中的最长子串。先把每个字符串翻转接到后面再连起来。二分答案分组height。考察是否有一组里面包含了所有的原区间或者翻转后的区间。

题题题

l u o g u P 3966 luoguP3966 luoguP3966
①:一开始把这些串拼接起来,然后求一个前缀哈希值。对于每个串,枚举它开始的匹配的位置。用前缀hash值算一下就知道是否匹配了。
②:把hash判断改成用sa判断。求两个后缀的LCP看它是不是长度大于等于匹配串的长度。注意中间要用最大的字符链接。
③:优化:因为LCP是求区间RMQ,所以对于每一个串。向左向右扩展。能扩展到的最远的长度就是答案。暴力是最坏O(n^2),二分是O(nlogn)。正确性说明,考虑②的过程,也是对于一个固定的模式串,枚举所有可行的起点。这里只不过是按照 r n k rnk rnk的从近到远顺序枚举的。减少了很多冗余。
④:如果是写二分的话,由于height不是单调的。所以要用ST表维护区间最小值了。

l u o g u P 4248 luoguP4248 luoguP4248
发现左边式子可以直接算。就只要算任意两队LCP的和就好了。
f [ i ] f[i] f[i]表示 i i i为右端点的答案,发现LCP是区间 h e i g h t height height的最小值。所以维护一个单调栈表示最近的 h e i g h t [ j ] &lt; = h e i g h t [ i ] height[j]&lt;=height[i] height[j]<=height[i]的地方。 [ j + 1 , i ] [j+1,i] [j+1,i]的答案都是 h e i g h t [ i ] height[i] height[i]。所以转移就是 f [ i ] + = f [ j ] + ( i − j ) ∗ h e i g h t [ i ] f[i]+=f[j] + (i-j)*height[i] f[i]+=f[j]+(ij)height[i]。每次 a n s + = f [ i ] ans+=f[i] ans+=f[i]就好了。 i , j i,j i,j任意需要 ∗ 2 *2 2

l u o g u P 3181 luoguP3181 luoguP3181
①: O ( n 2 ) O(n^2) On2暴力,枚举两个子串的开头。求一个后缀的LCP,得到的就是那个最长的子串。只计数包含两个开头端点的对数就可以。
②:这玩意也是一个LCP的和的形式,仿照上题利用单调栈维护就行。不过要注意这题的 i , j i,j i,j要分属左右两边。要么先算全部的,在分别减去 i , j i,j i,j都属于一边的。要算三遍SA。 要么在单调栈里面改改。

l u o g u P 3763 luoguP3763 luoguP3763

①:把两个字符串拼接起来,中间用没有出现过的最小字符’#'连接。

②:求完 S A , H e i g h t SA,Height SAHeight之后暴力。枚举匹配的首位,暴力记录当前有多少位不匹配。如果当前为不匹配就加1,否则指针就往后跳它们的 l c p lcp lcp这么多。。 O ( n l o g n ) O(nlogn) O(nlogn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值