结束位置集与状态

结束位置集

对于 $S$ 的一个子串 $t$,我们将 $S$ 中 $t$ 的所有结束位置叫做一个结束位置集,记为 $endpos(t)$。

状态:

我们将 $ S$ 的子串按照 $endpos$ 几何被分为若干个等价类,每一个等价类就对应于后缀自动机的一个状态。

性质:

我们设 $len(t_1)<=len(t_2)$,那么 $t_1$ 是 $t_2$ 的后缀当且仅当 $endpos(t_2) \subseteq endpos(t_1)$,$t_1$ 不是 $t_2$ 的后缀当且仅当 $endpos(t_1)\bigcap endpos(t_2)=\emptyset$。

一个状态所对应的所有子串,其中短的子串一定是长的子串的一个后缀,且子串的长度是连续的。也就是说,一个状态对应的所有子串,都是其中最长子串的一系列连续后缀。

后缀链接

定义:

我们考虑 $S$ 的一个前缀 $S[1,2,…,i]$,它会有 $i$ 个连续的后缀,这组连续的后缀可能会在中间若干个地方「断掉」并分为若干段,其中每一段的连续后缀都对应一个状态。我们可以用一个叫后缀链接的东西把这若干段链接起来。

后缀链接不属于DFA(有限状态自动机)的一部分,由于它奇妙的性质和强大的功能,构造 SAM 时会需要后缀链接。

性质:

所有后缀链接构成一棵根节点为 $t_0$(初始状态)的树。后缀链接构成的树本质上是 $endpos$ 集合构成的一棵树,树的边表示 $endpos$ 集合的包含关系。

构造算法

我们先用一个结构体来储存一个状态内的信息。其中 $len$ 该为对应子串的最长长度,$fa$ 为该状态的后缀链接,$next$ 为该状态的转移。

struct State {int len, fa, next[26]...;} st[MAXN*2];

假设我们构造好了 $S[1,2,..,i]$ 的SAM,并向其中添加字符 $s[i+1]$,那么我们就要对 $S[1,2,..,i]$ 所有后缀所对应的状态增加相应的转移,并且可以发现所有这些状态都被一条后缀链接构成的路径连接到初始状态上。我们只需要新建一个状态 $cur$,并对这条路径上的状态增加相应的转移就好。这个转移一共有三种情况:

int cur=++tot; st[cur].len=st[last].len+1;

先考虑最简单的情况,就是路径上的所有状态都没有 $s[i+1]$ 的转移。在这种情况下只要简单地把路径上的状态增加一个 $s[i+1]$ 的转移并指向状态 $cur$,再将 $cur$ 的后缀链接连向初始状态。

for(; p && !st[p].next[c]; p=st[p].fa) st[p].next[c]=cur;
if(!p) st[cur].fa=1;

再考虑第二种情况,路径上有一个状态 $p$ 有 $s[i+1]$ 的转移,设这个转移指向状态 $q$,且 $q$ 的 $len$ 等于 $p$ 的 $len+1$,也就是说 $q$ 对应的最长子串是 $p$ 对应的最长子串加 $s[i+1]$。这样我们只要把 $p$ 之前的状态增加一个 $s[i+1]$ 的转移并指向状态 $cur$,再将 $cur$ 的后缀链接连向 $q$。

int q=st[p].next[c];
if(st[p].len+1==st[q].len) st[cur].fa=q;

最后考虑最复杂的一种情况,路径上有一个状态 $p$ 有 $s[i+1]$ 的转移,设这个转移指向状态 $q$,但 $q$ 的 $len$ 不等于 $p$ 的 $len+1$,也就是说 $q$ 对应的最长子串不是 $p$ 对应的最长子串加 $s[i+1]$。这里我们要将状态 $q$ 分裂开,新建一份 $q$ 的拷贝 $t$,令 $t$ 状态对应的最长子串为 $p$ 对应的最长子串加 $s[i+1]$ 且令 $p$ 之后的状态的 $s[i+1]$ 转移指向的状态由 $q$ 变为 $t$,最后,将状态 $q$ 和 $cur$ 的后缀链接指向 $t$。

int t=++tot;
st[t].len=st[p].len+1;
memcpy(st[t].next, st[q].next, sizeof(st[q].next));
st[t].fa=st[q].fa;
for(; p && st[p].next[c]==q; p=st[p].fa) st[p].next[c]=t;
st[q].fa=st[cur].fa=t;

最终代码

int len, tot=1, last=1;
char s[MAXN];

struct State {int len, fa, next[26];} st[MAXN*2];

void insert(int c)
{
    int cur=++tot, p=last;
    st[cur].len=st[last].len+1;
    for(; p && !st[p].next[c]; p=st[p].fa) st[p].next[c]=cur;
    if(!p) st[cur].fa=1;
    else
    {
        int q=st[p].next[c];
        if(st[p].len+1==st[q].len) st[cur].fa=q;
        else
        {
            int t=++tot;
            st[t].len=st[p].len+1;
            memcpy(st[t].next, st[q].next, sizeof(st[q].next));
            st[t].fa=st[q].fa;
            for(; p && st[p].next[c]==q; p=st[p].fa) st[p].next[c]=t;
            st[q].fa=st[cur].fa=t;
        }
    }
    last=cur;
}

int main()
{
    scanf("%s", s+1);
    len=strlen(s+1);
    for(rint i=1; i<=len; ++i) insert(s[i]-'a');
    /*
    进行操作
    */
    return 0;
}

例题

1.hihocoder1445 重复旋律5

代码链接

2.hihocoder1457 重复旋律7

代码链接

3.BZOJ3998 弦论

代码链接

4.hihocoder1465 重复旋律8

代码链接

5.BZOJ4566 找相同字符

代码链接

6.hihocoder1449 重复旋律6

代码链接

7.BZOJ2882 工艺

代码链接

8.hihocoder1449 重复旋律9

代码链接

发表评论

邮箱地址不会被公开。 必填项已用*标注