天天看點

Luogu 3649 [APIO 2014] 回文串

          • 傳送門
          • 思路
          • Manacher 算法
            • 特殊字元
            • 回文半徑
            • 算法與實作
            • 本質不同的回文串個數
          • 正解
          • 參考代碼
          • 總結
傳送門
思路

  唉,我太弱了,什麼都不會,這道題各路神仙都說是模闆題,但我覺得完全不可做,唉,我太弱啦!神仙們太強啦!

  我不打算再學回文樹1了,我打算用神仙流傳的 Manacher + SAM 來做。在此之前,我們先回顧一下 Manacher 算法以及回文串的相關知識。

Manacher 算法

特殊字元

  Manacher 算法要在每兩個字元間插入一個從未出現過的特殊字元(包括首位),也就是說長度為 n n 的字元串将會插入 n+1n+1 個特殊字元。約定我們的特殊字元為 #。

  插入特殊字元後,所有回文串的長度都變成了奇數,也就是說回文串的對稱軸一定在一個字元上。

回文半徑

  回文半徑表示一個回文串最左或者最右的字元與其對稱軸的距離,準确地說是對稱軸到邊界的字元數(包含對稱軸和邊界)。

  我們設 RLi R L i 表示以第 i i 個字元為對稱軸的最長回文串的回文半徑。

  RLiRLi 還表示以 i i 為對稱軸的最長回文串長度加一。

算法與實作

  自己想。百度去。

本質不同的回文串個數

  定理:本質不同的回文串個數為 O(n)O(n)。

證明

  考慮 Manacher 算法的過程。當我們擴張觸及範圍時,說明我們可能找到一個新的回文子串。如果我們沒有擴張,那麼這個回文子串一定已經被找到過了。

  由于擴張次數是 O(n) O ( n ) 的,是以本質不同的回文子串是 O(n) O ( n ) 的。

  證畢。

  推論:本質不同的回文串的總長度為 O(n2) O ( n 2 ) 。

證明

  由定理,顯然。我們也可以考慮構造這樣一個字元串: aaaaa⋯ a a a a a ⋯ ,顯然本質不同的回文串的總長度是 O(n2) O ( n 2 ) 的。

正解

  我們來整理一下思路。顯然我們可以通過 Manacher 算法得到本質不同的所有回文串,個數為 O(n) O ( n ) 個。如果我們知道了各回文串的出現次數,問題就解決了。

  提到求各回文串的出現次數,自然會想到字尾自動機。那麼我們再來回憶一下字尾自動機。我們可以求出 right r i g h t 集合的大小,然後把每個字元串放到字尾自動機上跑,停在的結點就是出現次數。

  但是以上操作的時間複雜度與串總長同階,是以以上算法的時間複雜度是 O(n2) O ( n 2 ) 的。 有沒有加速這個過程的方法呢?

  假設某個子串以第 i i 個字元結尾,那它一定是 1∼i1∼i 的字尾,換句話說,它在字尾連結樹中一定是 i i 對應結點的祖先。我們倍增向上跳,直到長度滿足要求即可。這樣,單次查詢的時間複雜度為 O(logn)O(log⁡n),算法的整體時間複雜度為 O(nlogn) O ( n log ⁡ n ) 。

參考代碼
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
    INT_PUT a = ; bool positive = true;
    char ch = getchar();
    while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
    if (ch == '-') { positive = false; ch = getchar(); }
    while (std::isdigit(ch)) { a = a *  - (ch - '0'); ch = getchar(); }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[]; int length = ;
    if (x < ) putchar('-'); else x = -x;
    do buffer[length++] = -(x % ) + '0'; while (x /= );
    do putchar(buffer[--length]); while (length);
}

const int maxn = int() + ;
int n;
char str[maxn];
void readstr()
{
    char ch = getchar();
    while (!std::isalpha(ch)) ch = getchar();
    while (std::isalpha(ch))
    {
        str[++n] = ch;
        ch = getchar();
    }
}

struct SuffixAutomaton
{
    static const int alphabet = ;
    static inline int code(char ch) { return ch - 'a'; }
    struct Node
    {
        int len;
        int suffix;
        int ch[alphabet];
    } nodes[maxn * ];
    int size, last;
    SuffixAutomaton() { nodes[last = size++].suffix = -; }

    int cnt;
    int leaf[maxn];

    void extend(char ch)
    {
        int x = code(ch);
        int p = last;
        int cur = leaf[++cnt] = last = size++;
        nodes[cur].len = nodes[p].len + ;

        for (; ~p && !nodes[p].ch[x]; p = nodes[p].suffix)
            nodes[p].ch[x] = cur;
        if (!~p)
        {
            nodes[cur].suffix = ;
            return;
        }
        int q = nodes[p].ch[x];
        if (nodes[p].len +  == nodes[q].len)
        {
            nodes[cur].suffix = q;
            return;
        }

        int clone = size++;
        nodes[clone] = nodes[q];
        nodes[clone].len = nodes[p].len + ;
        nodes[q].suffix = nodes[cur].suffix = clone;
        for (; ~p && nodes[p].ch[x] == q; p = nodes[p].suffix)
            nodes[p].ch[x] = clone;
    }

    int k;
    int parent[][maxn * ];
    int buf[maxn * ];
    int sort[maxn * ];
    int weight[maxn * ];
    void init()
    {
        while ( << k <= size) k++;
        for (int i = ; i <= size; i++)
            parent[][i] = nodes[i].suffix;
        for (int i = ; i <= k; i++)
            for (int j = ; j <= size; j++)
                parent[i][j] = parent[i - ][parent[i - ][j]];

        for (int i = ; i < size; i++) buf[nodes[i].len]++;
        for (int i = ; i < size; i++) buf[i] += buf[i - ];
        for (int i = size - ; ~i; i--) sort[--buf[nodes[i].len]] = i;

        for (int i = ; i <= n; i++)
            weight[leaf[i]] = ;
        for (int i = size - ; i; i--)
        {
            int node = sort[i];
            weight[parent[][node]] += weight[node];
        }
    }
    LL query(int pos, int len)
    {
        int node = leaf[pos];
        for (int i = k; ~i; i--)
        {
            if (nodes[parent[i][node]].len < len) continue;
            node = parent[i][node];
        }
        return (LL)len * weight[node];
    }
} sam;

struct Manacher
{
    int size;
    char str[maxn * ];
    int f[maxn * ];

    void init()
    {
        str[] = '#';
        for (int i = ; i <= n; i++)
        {
            str[(i << ) - ] = ::str[i];
            str[i << ] = '#';
        }
        size =  * n + ;
    }

    void manacher(const std::function<void(int, int)> func)
    {
        int center = ;
        int maxr = ;
        for (int i = ; i < size; i++)
        {
            if (i <= maxr)
                f[i] = std::min(f[(center << ) - i], maxr - i + );
            else
                f[i] = ;

            while (i - f[i] >=  && i + f[i] < size && str[i - f[i]] == str[i + f[i]])
            {
                f[i]++;
                if (i + f[i] -  > maxr)
                {
                    maxr = i + f[i] - ;
                    center = i;
                    if (str[maxr] != '#')
                        func(((maxr + ) >> ), f[i]);
                }
            }
        }
    }
} manacher;

LL ans;
void callback(int pos, int len)
{
    ans = std::max(ans, sam.query(pos, len));
}

void run()
{
    readstr();

    for (int i = ; i <= n; i++)
        sam.extend(str[i]);
    sam.init();
    manacher.init();

    manacher.manacher(callback);
    printOut(ans);
}

int main()
{
    run();
    return ;
}
           
總結

  字尾自動機第一次跳躍要重定向至

cur

  Manacher 一般是先擴張,再更新

maxr

,這裡需要同時更新

maxr

。當然,第一步是要确定答案下界。

  1. 什麼是回文樹?回文樹又稱回文自動機(但是字尾樹不是字尾自動機),它在處理回文子串時有超強功效。 ↩