(同步个人博客 http://sxysxy.org/blogs/41 到csdn)
如题
为什么会有这样的需求?像我这只会写后缀自动机的选手遇到LCP相关的问题就一脸无奈,而用后缀数组可以解决这样的问题。而且,不管怎么讲,还是后缀自动机时间复杂度低啊!要是能用后缀自动机弄出来后缀数组那就非常妙了(好,常数这个东西啊...qaq)
后缀自动机
代码简单,易于理解,在线,跑得快。
后缀自动机 -> 后缀排序
首先后缀数组是实现了对后缀的排序。在 http://sxysxy.org/blogs/23 这里面搞过这个东西。然而当初太过Naïve,其实那样是可以被卡到n^2的,那就不清真了。不过总体思路是正确的,就是对后缀自动机按字典序dfs。但是要进行一些优化,加速这个过程。
回顾一下
后缀自动机的一个重要性质:对于一个状态:(人懒就直接贴之前那篇文章里面的截图了)
后缀自动机 -> 后缀数组
优美的姿势 : 与之前那篇文章讲的一样,先对能整个字符串的后缀的状态进行一个标记,然后 dfs 自动机。在dfs过程中加速。加速过程有些类似并查集的路径压缩。细节在下面的代码里。
下面是这个 http://uoj.ac/problem/35 的AC代码
#include <cstdio>
#include <cstring>
using namespace std;
// UOJ35:后缀排序
#define ALPHA 26
#define BASE 'a'
#define MAXN 100005
class SAM
{
int last, size;
public:
struct state
{
int len, link;
int next[ALPHA];
int fast, fast_len; //加速到达的状态,省下的距离
bool mark;
void init()
{
len = fast = fast_len = 0;
link = -1;
mark = false;
memset(next, 0, sizeof(next));
}
}st[MAXN << 1];
int newst()
{
st[size++].init();
return size-1;
}
SAM()
{
last = size = 0;
newst();
}
void expand(int c)
{
int cur = newst();
st[cur].len = st[last].len + 1;
int p;
for(p = last; p != -1 && !st[p].next[c]; p = st[p].link)
st[p].next[c] = cur;
if(p == -1)st[cur].link = 0;
else
{
int q = st[p].next[c];
if(st[q].len == st[p].len + 1)st[cur].link = q;
else
{
int clone = newst();
st[clone].len = st[p].len+1;
st[clone].link = st[q].link;
memcpy(st[clone].next, st[q].next, sizeof(st[clone].next));
for(; p != -1 && st[p].next[c] == q; p = st[p].link)
st[p].next[c] = clone;
st[q].link = st[cur].link = clone;
}
}
last = cur;
}
void mark_suffix() //标记后缀
{
//从last出发
for(int p = last; p != -1; p = st[p].link)
st[p].mark = true;
}
void calc_fast()
{
for(int p = 0; p < size; p++)
{
int q, f = 0;
for(int i = 0; i < ALPHA; i++)
{
if(st[p].next[i])
{
f++;
q = st[p].next[i];
}
}
//先计算这个一定能进行的加速。
if(f == 1 && !st[p].mark)
{
st[p].fast = q;
st[p].fast_len = 1;
}
}
}
int fast_go(int s)
{
//合并加速路径(我也就只能用"合并"这个词了....)
int p, sum = 0;
for(p = s; st[p].fast; p = st[p].fast)
sum += st[p].fast_len;
//现在,p成为了s能加速到的最终目的地。
//下面把s -> p路径上所有的状态的加速目的地都指向p,
//这就很类似并查集的路径压缩
for(int q = s; st[q].fast; q = st[q].fast)
{
sum -= st[q].fast_len;
st[q].fast = p;
st[q].fast_len += sum; //加速的距离要重新计算。
}
return p; //...p是s -> p终点
}
int top;
int *psa;
void dfs(int u, int d)
{
if(st[u].mark)psa[top++] = d; //mark过的,这是一个后缀
for(int i = 0; i < ALPHA; i++)
{
if(st[u].next[i]) //是一个子节点
{
state &go = st[st[u].next[i]];
fast_go(st[u].next[i]); //考虑加速
if(go.fast)
dfs(go.fast, d-1-go.fast_len); //可以加速
else
dfs(st[u].next[i], d-1); //0。0
}
}
}
void buildSA(int len, int *buf)
{
//总的套路,显然这个是离线的qaq
mark_suffix();
calc_fast();
top = 0;
psa = buf;
dfs(0, len);
}
}sam;
int sa[MAXN];
char buf[MAXN];
int height[MAXN];
int rank[MAXN];
void calc_height(int n)
{
char *p = buf-1;
for(int i = 1; i <= n; i++)
rank[sa[i]] = i;
int k = 0;
for(int i = 1; i <= n; i++)
{
if(k)k--;
int j = sa[rank[i]-1];
while(p[i+k] == p[j+k])k++;
height[rank[i]] = k;
}
}
int main()
{
scanf("%s", buf);
int len = strlen(buf);
for(int i = 0; i < len; i++)
sam.expand(buf[i]-BASE);
sam.buildSA(len+1, sa);
for(int i = 1; i <= len; i++)
printf("%d ", sa[i]);
puts("");
calc_height(len);
for(int i = 2; i <= len; i++)
printf("%d ", height[i]);
puts("");
return 0;
}