後綴排序 後綴自動機的應用

同步個人博客 http://sxysxy.org/blogs/23 到csdn)

給一字符串的所有後綴排個序輸出,字符串長度 <= 100000

排序要求字典序小的在前面,在滿足這個條件的情況下,短的後綴放到前面

例如ababa的後綴排序結果是[a, aba, ababa, ba, baba]。

暴力做法很容易想到,枚舉出字符串s的總共length(s)個後綴,sort一下,然而time limit exceed (

把後綴都插入字典樹? time limit exceed & memory limit exceed。

所以這個時候就需要後綴自動機登場了。

後綴排序可以看作是給字符串所有子串排序的一個特殊情況,就是子串的右端點是字符串的末尾。

對與子串的排序,我們可以構造完後綴自動機然後dfs,每次先走字典序小的字符,最後遍歷完畢就是子串排序的結果。

只排序後綴呢?

今天上午英語課上我手畫了一個字符串的後綴自動機然後腦補出來了一個這樣的方法:

我們從後綴自動機最後一個狀態St出發,向上走後綴鏈接樹(也有叫”parent樹”的),途經的狀態節點可以接受的最長的字符串都是St可接受的最長的字符串的後綴。而St可接受的最長的字符串恰好就是整個字符串str(因爲St是最後一個狀態)。也就是說,從St出發走後綴鏈接一直到頂,恰好可以途徑字符串str的所有後綴。

那麼後綴排序就可以把從St出發走後綴鏈接到頂途徑的所有節點都標記一下,之後像子串排序一樣dfs這個後綴自動機,只在打了標記的點那裏輸出,就可以得到後綴排序的結果了。最後總的時間複雜度還是O(n),SAM很強啊!!!

這裏寫圖片描述

代碼

#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 1e6+2;
const int SIGMA = 26;
const int BASE = (int)'a';
class SAM
{
    public:
    int last, size;
    struct state
    {
        int link, len;
        int next[SIGMA];
        bool mark;
        void init()
        {
            link = -1;
            len = 0;
            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(char newc)
    {
        int c = newc-BASE;
        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[q].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;
    }
    char buf[MAXN];
    int top;
    void dfs(int u)
    {
        if(top && st[u].mark)puts(buf);
        state &cur = st[u];
        for(int i = 0; i < SIGMA; i++)
        {
            if(cur.next[i])
            {
                buf[top++] = i+BASE;
                buf[top] = 0;
                dfs(cur.next[i]);
                top--;
            }
        }
    }
    void suffix_sort()
    {
        top = 0;
            //從最後一個狀態開始,走後綴鏈接到頂,途徑節點打上標記。
        for(int p = last; p; p = st[p].link)
            st[p].mark = true;
        dfs(0);
    }
}sam;

int main()
{
    char c;
    while(c = getchar())
        if(c >= 'a' && c <= 'z')break;
    sam.expand(c);
    while(c = getchar())
        if(c >= 'a' && c <= 'z')sam.expand(c);
            else break;
    sam.suffix_sort();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章