其实SA这个东西很久之前就听过qwq

但是基本已经忘的差不多了

嘤嘤嘤

QWQ感觉自己不是很理解啊

所以写不出来那种博客

QWQ只能安利一些别人的博客了

真的是讲的非常好

不要在意名字

orz,膜拜他们

顺便弄上自己的代码(里面有一些需要注意的地方)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 2e6+1e2;
int wb[maxn];
int rk[maxn];
int sa[maxn],tmp[maxn];
char a[maxn];
int h[maxn],height[maxn];
int n;
void getsa()
{
int *x=rk,*y=tmp;
int s = 128;
for (int i=1;i<=n;i++) x[i]=a[i],y[i]=i; //初始每个长度为1的后缀的rank是他自己的字符大小,第二关键字相当于空,那么就顺次赋值为i
for (int i=1;i<=s;i++) wb[i] =0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++; // 这里其实基数排序的时候,x表示上一轮的rank,y[i]表示第二关键字排名为i的第一关键字的位置是多少
for (int i=1;i<=s;i++) wb[i]+=wb[i-1];//做前缀和就能更好的算出来排名,比如说有3个a,2个b,那么自然第一个b的排名就要从4开始
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i]; //只能感性理解了啊qwq之所以倒着枚举是为了保证在第一关键字相同的时候,第二关键字也是有序的
int p = 0;
for (int j=1;p<n;j<<=1) //p是指本质不同的串的个数
{
//x表示上一轮的rank
//y表示排名为i的第二关键字的第一关键字的位置是多少(空优先)
p=0;
//这里可以这么理解,如果一个串他的位置是大于n-j+1,那么他一定是没有第二关键字的。
for (int i=n-j+1;i<=n;i++) y[++p]=i; //第二关键字为空,就排名靠前
for (int i=1;i<=n;i++) if (sa[i]>j) y[++p]=sa[i]-j; //如果排名为i的位置是大于j的,那么他可以成为一个第二关键字,并且第一关键字的位置应该是sa[i]-j;
for (int i=1;i<=s;i++) wb[i]=0;
for (int i=1;i<=n;i++) wb[x[y[i]]]++;
for (int i=1;i<=s;i++) wb[i]+=wb[i-1];
for (int i=n;i>=1;i--) sa[wb[x[y[i]]]--]=y[i]; //这里i之所以从n开始,因为我们要保证排序第一关键字的时候,第二关键字一定也是符合原来的顺序的,就是说,原来第二关键字大的,一定在后面(这个是基数排序的思想)
swap(x,y);//交换之后,y表示上一轮的rank,x是一个新的数组
p=1;
x[sa[1]]=1;
//若两个串的两部分在上一轮rank都相等的话, 那么无法分辨,所以p不用加
for (int i=2;i<=n;i++) x[sa[i]] = (y[sa[i]]==y[sa[i-1]] && y[sa[i-1]+j]==y[sa[i]+j]) ? p : ++p;
s=p;
}
for (int i=1;i<=n;i++) rk[sa[i]]=i;
h[0]=0;
for (int i=1;i<=n;i++)
{
//h[i]表示i号后缀与它前一名的后缀的最长公共前缀
//height[i]表示排名为i的后缀和排名为i-1的后缀的lcp
h[i]=max(h[i-1]-1,0);
while (i+h[i]<=n && sa[rk[i]-1]+h[i]<=n && a[i+h[i]]==a[sa[rk[i]-1]+h[i]])
h[i]++;
}
for (int i=1;i<=n;i++) height[i]=h[sa[i]];
}
int main()
{
scanf("%s",a+1);
n=strlen(a+1);
getsa();
for (int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}

Update

整理一些\(SA\)的小性质和经典应用。(会持续更新的)

1.求两个后缀的\(lcp\) ,应该是\(min(height[rk[i]+1],height[rk[i]+2].....height[rk[j]])\)

2.对于排名为\(i\)的后缀,与它\(lcp\)最长的后缀应该是排名为\(i-1\),(可以理解为越靠前差异越多,越靠前,取\(min\)的区间就越长)

3.最长可重叠重复子串,应该是\(max(height[i])\)(这里把子串看成后缀的前缀,同时依据性质2就能得到)

4.给定一个子串,求不相同子串的个数,这里要这么考虑,按照字典序加入,每加入一个字符串,会新增加\(n-sa[i]+1\)个新的子串,但是会重复\(height[i]\)个,\((只有lcp会重复,同时依据性质2)\)

5.给定两个串,求他们的最长公共子串。

将B串拼到A串后面,然后中间添加一个非法字符,然后直接想询问最大的lcp(保证\(sa[i]和sa[i-1]\)分别位于两个串即可)

6.给定两个串,求他们的公共子串数目。

将B串拼到A串后面,然后中间添加一个非法字符,然后对于每个\(height\)用单调栈维护出左右最远能扩展到哪里。然后\(ans\)加上\(height[i]*(geta(i-1,l[i]-1)*getb(r[i],i)+getb(i-1,l[i]-1)*geta(r[i],i))\)

这里之所以是这个式子的原因(第一要保证是一个端点属于A串,一个属于B串。另一个原因是因为对于一个扩展区间\([l,pos,r]\)来说,选择后缀的右端点是在\([pos,r]\)而左端点是\([l-1,pos-1]\),因为后缀的选择的左边对于\(height\)是开区间,参考性质1。

洛谷3809 SA模板 后缀数组学习笔记(复习)的更多相关文章

  1. 洛谷-P3809-后缀排序(后缀数组)

    看了求后缀数组的倍增法之后很快就理解了,但是自己写的倍增法用map排序还是超时了.然后看了两天别人写的模板,题目是通过了,但感觉代码还是半懂半背的.以后多熟悉熟悉吧: 后缀数组 #include &q ...

  2. 洛谷P3763 [TJOI2017]DNA(后缀数组 RMQ)

    题意 题目链接 Sol 这题打死我也不会想到后缀数组的,应该会全程想AC自动机之类的吧 但知道这题能用后缀数组做之后应该就不是那么难了 首先把\(S\)和\(S0\)拼到一起跑,求出Height数组 ...

  3. 洛谷 P4143 采集矿石 后缀数组

    题目背景 ZRQ 成功从坍塌的洞穴中逃了出来.终于,他看到了要研究的矿石.他想挑一些带回去完成任务. 题目来源:Zhang_RQ哦对了 \(ZRQ\) 就他,嗯 题目描述 ZRQ 发现这里有 \(N\ ...

  4. 洛谷P5108 仰望半月的夜空(后缀数组)

    题意 题目链接 Sol warning:下面这个做法只有95分,本地拍了1w+组都没找到错误我表示十分无能为力 我们考虑每个串的排名去更新答案,显然排名为\(1\)的后缀的前缀一定是当前长度的字典序最 ...

  5. 【洛谷P3369】普通平衡树——Splay学习笔记(一)

    二叉搜索树(二叉排序树) 概念:一棵树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 它的左.右子树也分别为二叉搜索树 ...

  6. 【洛谷P3391】文艺平衡树——Splay学习笔记(二)

    题目链接 Splay基础操作 \(Splay\)上的区间翻转 首先,这里的\(Splay\)维护的是一个序列的顺序,每个结点即为序列中的一个数,序列的顺序即为\(Splay\)的中序遍历 那么如何实现 ...

  7. [洛谷P3809]【模板】后缀排序

    [洛谷P3809][模板]后缀排序 题目大意: 对于给定的长度为\(n(n\le10^6)\)的字符串求后缀数组\(sa[i]\). 思路: 倍增+快排构造后缀数组.代码参考<挑战程序设计竞赛& ...

  8. 洛谷 P3919 【模板】可持久化数组(可持久化线段树/平衡树)-可持久化线段树(单点更新,单点查询)

    P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目背景 UPDATE : 最后一个点时间空间已经放大 标题即题意 有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集 ...

  9. 洛谷 P3387 【模板】缩点 DAGdp学习记

    我们以洛谷P3387 [模板]缩点 来学习DAGdp 1.这道题的流程 //伪代码 for i->n if(i未被遍历) tarjan(i) 缩点() DAGdp() 完成 首先tarjan这部 ...

随机推荐

  1. golang中的三个点 ‘...‘ 的用法

    '-' 其实是go的一种语法糖. 它的第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数. 第二个用法是slice可以被打散进行传递. 下面直接上例子: func test1( ...

  2. centos7 误用 cat 打开了一个很大的文件

    2021-09-01 1. 问题描述 刚才看到一个文件,出于好奇我就直接用 cat 命令查看了一下,结果文件巨大,一直刷屏停不下来 2. 解决方法 克隆一个窗口,抓一下这个 cat 进程,再使用 ki ...

  3. 字符串截取子串(Java substring , indexOf)

    前言 因为之前java课设做的是股票分析系统,我找的接口返回的是一个.csv文件,因为这种文件里面的数据是以逗号分隔的,所以要对数据进行分析的时候需要截取子串,并且以逗号作为截取的标志.所以接下来就说 ...

  4. 密钥交换协议之IKEv2

    1. IKEv2 1.1 IKEv2简介 IKEv2(Internet Key Exchange Version 2,互联网密钥交换协议第 2 版)是第 1 版本的 IKE 协议(本文简称 IKEv1 ...

  5. lua中的随机数

    Lua 生成随机数需要用到两个函数:math.randomseed(xx), math.random([n [, m]]) 1. math.randomseed(n) 接收一个整数 n 作为随机序列种 ...

  6. ZBLOG PHP调用相关文章列表以及上一篇下一篇文章代码

    如果是比较小的个人博客.专题类网站项目,老蒋还是比较喜欢使用ZBLOG PHP程序的,无论是轻便度还是易用性上比WordPress简单很多,虽然WP的功能很强大,比如强大的插件和主题丰富功能是当前最为 ...

  7. CodeForce-799B T-shirt buying (STL_set)

    有 n 件T恤.第 i 件T恤的价格为 pi .每个T恤有两面,第 i 件T恤正面颜色为 ai ,反面颜色为 bi . 有 m 个人想买T恤,每个人都恰好买一件.第 j 个人最喜欢颜色 cj. 一个人 ...

  8. 38KHz,NEC红外模拟发送和接收程序

    /*************************************************************************************************/ ...

  9. Dapr实战(一) 基础概念与环境搭建

    什么是Dapr Dapr 是一个可移植的.事件驱动的运行时,可运行在云平台或边缘计算中.支持多种编程语言和开发框架. 上面是官方对Dapr的介绍.有点难以理解,大白话可以理解为:Dapr是一个运行时, ...

  10. symfony中模板生成路径两种方式

    1. 使用url('route_a_b_c')  这种方式会是全路径 : http://www.test.com/a/b/c 2. 使用path('route_a_b_c') 这种方式只是路径: /a ...