• [笔记]后缀数组SA


    参考资料:
    1.后缀数组详解
    2.后缀数组-学习笔记
    3.后缀数组——处理字符串的有力工具

    定义

    \(SA\)排名为\(i\)的后缀的位置
    \(rk\)位置为\(i\)的后缀的排名
    \(tp\)第二关键字的排名为\(i\)的后缀的位置,还被用作\(rank\)的暂存
    \(tax\)每个排名对应的后缀数量
    后缀数组就是为了求出\(sa\)\(rk\)

    性质

    \(rk[sa[i]]=i\) \(sa[rk[i]]=i\)

    $LCP(x,y) $:字符串x与字符串y的最长公共前缀,在这里指x号后缀与与y号后缀的最长公共前缀

    \(height[i]=lcp ( sa[i],sa[i - 1] )\),即排名\(i\)的后缀与排名\(i−1\)的后缀的最长公共前缀

    \(H[i]:height[rak[i]]\),即\(i\)号后缀与它前一名的后缀的最长公共前缀

    \(H[i] \geqslant H[i - 1] - 1\) 证明

    $LCP(i,j)=LCP(j,i) $

    \(LCP(i,i)=len(sa[i])=n-sa[i]+1\)

    \(LCP(i,k)=min\left\{height[j] \right\}(i+1<=j<=k)\)

    \(S\)不同的子串个数\(\dfrac{n(n+1)}{2} -\sum_{i=1}^nheight[i]\)

    代码

    #include <iostream> 
    #include <cstdio>
    #include <string>
    #define R register int
    
    using namespace std;
    const int N = 1000005;
    string s;
    /* sa[i]:排名为i的后缀的位置
    rak[i]:从第i个位置开始的后缀的排名,下文为了叙述方便,把从第i个位置开始的后缀简称为后缀i
    tp[i]:基数排序的第二关键字,意义与sa一样,即第二关键字排名为i的后缀的位置
    tax[i]:i号元素出现了多少次。辅助基数排序
    s:字符串,s[i]表示字符串中第i个字符串*/
    int n, m, sa[N], rk[N], tp[N], c[N];
    void _sort() {
    	for(R i = 1; i <= m; ++i) c[i] = 0;
    	for(R i = 1; i <= n; ++i) c[rk[i]]++;
    	for(R i = 1; i <= m; ++i) c[i] += c[i - 1];
    	for(R i = n; i >= 1; --i) sa[c[rk[tp[i]]]--] = tp[i];
    }
    void SA() {
    	m = 150;
    	for(R i = 1; i <= n; ++i) rk[i] = s[i - 1], tp[i] = i;
    	_sort();
    	for(R w = 1, p = 0; p < n && w <= n; m = p, w <<= 1) {
    		p = 0;
    		for(R i = 1; i <= w; ++i) tp[++p] = n - w + i;
    		for(R i = 1; i <= n; ++i) if(sa[i] > w) tp[++p] = sa[i] - w;
    		_sort();
    		swap(tp, rk);
    		rk[sa[1]] = p = 1;
    		for(R i = 2; i <= n; ++i)
    			rk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]] && tp[sa[i - 1] + w] == tp[sa[i] + w])
    			? p : ++p;
    	}
    }
    /*i号后缀:从i开始的后缀
    lcp(x,y):字符串x与字符串y的最长公共前缀,在这里指x号后缀与与y号后缀的最长公共前缀
    height[i]:lcp(sa[i],sa[i-1]),即排名为i的后缀与排名为i-1的后缀的最长公共前缀
    H[i]:height[rak[i]],即i号后缀与它前一名的后缀的最长公共前缀*/
    int Height[N];
    void Get() {
    	int j, k = 0;
    	for(int i = 1; i <= n; i++) {
    		if(k) k--;
    		j = sa[rk[i] - 1];
    		while(s[i + k - 1] == s[j + k - 1]) ++k;
    		Height[rk[i]] = k;
    	}
    }
    int main()
    {
    	cin >> s;
    	n = s.length();
    	SA();
    	for(R i = 1; i <= n; ++i) printf("%d ", sa[i]);
    	cout << endl;
    	Get();
    	return 0;
    }
    

    Problem

    P2408 不同子串个数

    \(ans=\dfrac{n(n+1)}{2} -\sum height[i]\)

    Luogu

    P3809 【模板】后缀排序
    P4070 [SDOI2016]生成魔咒
    P4051 [JSOI2007]字符加密
    P2463 [SDOI2008]Sandy的卡片
    P2408 不同子串个数
    
  • 相关阅读:
    多线程之旅:避免死锁——简单的锁分级(锁排序)
    和我一起来学iOS(三)UIView及其子类(上)
    谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用
    和我一起来学iOS(二)iOS中的一些约定、模式与三种回调机制
    多线程之旅六——异步编程模式,自己实现IAsyncResult
    详解JavaScript中的函数与闭包
    和我一起来学iOS(一)ObjectC的语法
    浅谈SQL SERVER中的物理联接算法
    多线程之旅之三——Windows内核对象同步机制
    深入 聚集索引与非聚集索引(一)
  • 原文地址:https://www.cnblogs.com/shiokiri/p/10572126.html
Copyright © 2020-2023  润新知