1 条题解

  • 0
    @ 2025-8-24 22:09:53

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar xht
    好想爱这个世界啊

    搬运于2025-08-24 22:09:53,当前版本为作者最后更新于2019-05-06 18:36:04,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目地址:【模板】树上后缀排序

    我们尝试把普通 SA 改成树上 SA,所以先把普通 SA 贴上来。

    namespace SA {
        int sa[N], rk[N], tp[N], tx[N];
        
        inline void tsort() {
            for (int i = 1; i <= m; i++) tx[i] = 0;
            for (int i = 1; i <= n; i++) ++tx[rk[i]];
            for (int i = 1; i <= m; i++) tx[i] += tx[i-1];
            for (int i = n; i; i--) sa[tx[rk[tp[i]]]--] = tp[i];
        }
    
        inline bool pd(int i, int w) {
            return tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w];
        }
    
        inline void main() {
            for (int i = 1; i <= n; i++) rk[i] = s[i] - 'a' + 1, tp[i] = i;
            tsort();
            for (int w = 1, p = 0; p < n; w <<= 1, m = p) {
                p = 0;
                for (int i = 1; i <= w; i++) tp[++p] = n - w + i;
                for (int i = 1; i <= n; i++) if (sa[i] > w) tp[++p] = sa[i] - w;
                tsort(), swap(rk, tp), rk[sa[1]] = p = 1;
                for (int i = 2; i <= n; i++) rk[sa[i]] = pd(i, w) ? p : ++p;
            }
        }
    }
    

    想要把普通 SA 改成树上 SA,仔细观察上面的代码可以发现:

    1. tsorttsort 函数肯定是不用改的;
    2. pdpd 函数可以用树上倍增实现;
    3. mainmain 函数似乎也很好改?

    于是开始改改改,突然发现有个问题:由于序列上每个后缀长度都不一样,所以不可能出现完全相同的字符串,可是在树上是可能出现这种情况的。

    然后就没办法了么?

    办法肯定是有的要不然这道题是咋出出来的

    我们来思考一下,在倍增的每一轮,基数排序究竟要达到什么目的?

    对于普通 SA,在倍增的每一轮,假设已经对所有长度为 xx 的串排好序了。“第一关键字”和“第二关键字”代表了两个首尾相接的长度为 xx 的串,称为“主串”和“次串”。基数排序通过 O(n)O(n) 的时间,将每一对“主串”和“次串”合并成一个长度为 2x2x 的新串并保持合并后有序

    这样可以保证 O(logn)O(\log n) 次后,所有后缀呈有序状态。

    对于树上 SA,也是同样的。只不过,除了“主串”作为第一关键字,“次串”作为第二关键字以外,为了保证合并后的有序性,我们还要额外将上一轮的有序状态作为第三关键字。同时第二关键字也不能简单地用原先的 rkrk 数组构造(因为 rkrk 数组会出现相同的排名),而要额外使用没有重复的数组(下面代码中的 rkkrkk 数组)构造。

    总而言之,我们需要使用两次基数排序来达到目的,具体实现请参考代码因为这说得实在是太抽象了

    namespace SA {
        int sa[N], rk[N], rkk[N], tp[N], rk2[N], tx[N];
    
        inline void tsort(int *sa, int *rk, int *tp, int m) {
            for (int i = 0; i <= m; i++) tx[i] = 0;
            for (int i = 1; i <= n; i++) ++tx[rk[i]];
            for (int i = 1; i <= m; i++) tx[i] += tx[i-1];
            for (int i = n; i; i--) sa[tx[rk[tp[i]]]--] = tp[i];
        }
    
        inline bool pd(int i, int t) {
            return tp[sa[i-1]] == tp[sa[i]] && tp[f[t][sa[i-1]]] == tp[f[t][sa[i]]];
        }
    
        inline void main() {
            int p = 0;
            for (int i = 1; i <= n; i++) a[i] = s[i] - 'a' + 1, tp[i] = i;
            tsort(sa, a, tp, n);
            rk[sa[1]] = rkk[sa[1]] = p = 1;
            for (int i = 2; i <= n; i++) {
                rk[sa[i]] = a[sa[i-1]] == a[sa[i]] ? p : ++p;
                rkk[sa[i]] = i;
            }
            for (int w = 1, t = 0; w < n; w <<= 1, ++t) {
                for (int i = 1; i <= n; i++) rk2[i] = rkk[f[t][i]];
                tsort(tp, rk2, sa, n);
                tsort(sa, rk, tp, p);
                swap(rk, tp);
                rk[sa[1]] = rkk[sa[1]] = p = 1;
                for (int i = 2; i <= n; i++) {
                    rk[sa[i]] = pd(i, t) ? p : ++p;
                    rkk[sa[i]] = i;
                }
            }
        }
    }
    
    • 1

    信息

    ID
    4309
    时间
    1000ms
    内存
    500MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者