Extensión común más larga / LCE | Conjunto 2 (Reducción a RMQ)

Requisitos previos: 

El problema de la extensión común más larga (LCE) considera una string s y calcula, para cada par (L , R), la substring más larga de s que comienza tanto en L como en R. En LCE, en cada una de las consultas que tenemos que responder la longitud del prefijo común más largo que comienza en los índices L y R.

Ejemplo: 

String : «abbababba» 
Consultas: LCE (1, 2), LCE (1, 6) y LCE (0, 5) 

Encuentre la longitud del prefijo común más largo que comienza en el índice dado como (1, 2), (1, 6) y (0, 5) .
La string resaltada es el prefijo común más largo que comienza en el índice L y R de las consultas respectivas. Tenemos que encontrar la longitud del prefijo común más largo que comienza en index- (1, 2), (1, 6) y (0, 5) .

Longest Common Extension

En el Conjunto 1 , explicamos el método ingenuo para encontrar la longitud del LCE de una string en muchas consultas. En este conjunto, mostraremos cómo un problema LCE puede reducirse a un problema RMQ, por lo tanto, disminuyendo la complejidad del tiempo asintótico del método ingenuo.

Reducción de LCE a RMQ

Deje que la string de entrada sea S y que las consultas tengan la forma LCE(L, R) . Deje que la array de sufijos para s sea Suff[] y la array lcp sea lcp[] .
La extensión común más larga entre dos sufijos SL y S R de S se puede obtener de la array lcp de la siguiente manera. 

  • Sea bajo el rango de S L entre los sufijos de S (es decir, Suff[bajo] = L).
  • Sea alto el rango de S R entre los sufijos de S. Sin pérdida de generalidad, asumimos que bajo < alto.
  • Entonces la extensión común más larga de S L y S R es lcp(bajo, alto) = min (bajo<=k< alto) lcp [k].

Prueba: Sea S L = S L …S L+C …s n y S R = S R …S R+c …s n , y sea c la extensión común más larga de S L y S R (es decir, S L … S L+C-1 = s norte …S R+c-1 ). Suponemos que la string S tiene un carácter centinela, de modo que ningún sufijo de S es un prefijo de ningún otro sufijo de S que no sea él mismo.

  • Si bajo = alto – 1 entonces i = bajo y lcp[bajo] = c es la extensión común más larga de S L y S R y hemos terminado.
  • Si bajo < alto -1, seleccione i tal lcp[i] es el valor mínimo en el intervalo [bajo, alto] de la array lcp. Entonces tenemos dos casos posibles: 
    • Si c < lcp[i] tenemos una contradicción porque S L . . . S L+lcp[i]-1 = S R . . . S R +lcp[i]-1 por la definición de la tabla LCP y el hecho de que las entradas de lcp corresponden a sufijos ordenados de S.
    • si c > lcp[i], sea alto = Suff[i], de modo que S alto sea el sufijo asociado con la posición i. S i es tal que s alto . . . s alto+lcp[i]-1 = S L . . . S L+lcp[i]-1 y s alto . . . s alto+lcp[i]-1 = S R . . . S R+lcp[i]-1 , pero dado que S L . . . S L+c-1 = S R . . . S R+c-1 tenemos que la array lcp debería estar mal ordenada, lo cual es una contradicción.

Por lo tanto, tenemos c = lcp[i].
Por lo tanto, hemos reducido nuestra consulta de extensión común más larga a una consulta mínima de rango sobre un rango en lcp.

Algoritmo

  • Para encontrar bajo y alto, primero debemos calcular la array de sufijos y luego, a partir de la array de sufijos, calculamos la array de sufijos inversa.
  • También necesitamos la array lcp, por lo que usamos el algoritmo de Kasai para encontrar la array lcp a partir de la array de sufijos.
  • Una vez que se hacen las cosas anteriores, simplemente encontramos el valor mínimo en la array lcp desde el índice, de menor a mayor (como se demostró anteriormente) para cada consulta.

El valor mínimo es la longitud del LCE para esa consulta. 

Implementación 

C++

// A C++ Program to find the length of longest common
// extension using Direct Minimum Algorithm
#include<bits/stdc++.h>
using namespace std;
 
// Structure to represent a query of form (L,R)
struct Query
{
    int L, R;
};
 
// Structure to store information of a suffix
struct suffix
{
    int index;  // To store original index
    int rank[2]; // To store ranks and next rank pair
};
 
// A utility function to get minimum of two numbers
int minVal(int x, int y) { return (x < y)? x: y; }
 
// A utility function to get minimum of two numbers
int maxVal(int x, int y) { return (x > y)? x: y; }
 
// A comparison function used by sort() to compare
// two suffixes Compares two pairs, returns 1 if
// first pair is smaller
int cmp(struct suffix a, struct suffix b)
{
    return (a.rank[0] == b.rank[0])?
                   (a.rank[1] < b.rank[1]):
                   (a.rank[0] < b.rank[0]);
}
 
// This is the main function that takes a string 'txt'
// of size n as an argument, builds and return the
// suffix array for the given string
vector<int> buildSuffixArray(string txt, int n)
{
    // A structure to store suffixes and their indexes
    struct suffix suffixes[n];
 
    // Store suffixes and their indexes in an array
    // of structures.
    // The structure is needed to sort the suffixes
    // alphabetically and maintain their old indexes
    // while sorting
    for (int i = 0; i < n; i++)
    {
        suffixes[i].index = i;
        suffixes[i].rank[0] = txt[i] - 'a';
        suffixes[i].rank[1] =
                 ((i+1) < n)? (txt[i + 1] - 'a'): -1;
    }
 
    // Sort the suffixes using the comparison function
    // defined above.
    sort(suffixes, suffixes+n, cmp);
 
    // At his point, all suffixes are sorted according
    // to first 2 characters.  Let us sort suffixes
    // according to first 4/ characters, then first 8
    // and so on
 
    // This array is needed to get the index in suffixes[]
    // from original index.  This mapping is needed to get
    // next suffix.
    int ind[n];
 
    for (int k = 4; k < 2*n; k = k*2)
    {
        // Assigning rank and index values to first suffix
        int rank = 0;
        int prev_rank = suffixes[0].rank[0];
        suffixes[0].rank[0] = rank;
        ind[suffixes[0].index] = 0;
 
        // Assigning rank to suffixes
        for (int i = 1; i < n; i++)
        {
            // If first rank and next ranks are same as
            // that of previous/ suffix in array, assign
            // the same new rank to this suffix
            if (suffixes[i].rank[0] == prev_rank &&
                suffixes[i].rank[1] == suffixes[i-1].rank[1])
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = rank;
            }
            else // Otherwise increment rank and assign
            {
                prev_rank = suffixes[i].rank[0];
                suffixes[i].rank[0] = ++rank;
            }
            ind[suffixes[i].index] = i;
        }
 
        // Assign next rank to every suffix
        for (int i = 0; i < n; i++)
        {
            int nextindex = suffixes[i].index + k/2;
            suffixes[i].rank[1] = (nextindex < n)?
                           suffixes[ind[nextindex]].rank[0]: -1;
        }
 
        // Sort the suffixes according to first k characters
        sort(suffixes, suffixes+n, cmp);
    }
 
    // Store indexes of all sorted suffixes in the suffix array
    vector<int>suffixArr;
    for (int i = 0; i < n; i++)
        suffixArr.push_back(suffixes[i].index);
 
    // Return the suffix array
    return  suffixArr;
}
 
/* To construct and return LCP */
vector<int> kasai(string txt, vector<int> suffixArr,
                              vector<int> &invSuff)
{
    int n = suffixArr.size();
 
    // To store LCP array
    vector<int> lcp(n, 0);
 
    // Fill values in invSuff[]
    for (int i=0; i < n; i++)
        invSuff[suffixArr[i]] = i;
 
    // Initialize length of previous LCP
    int k = 0;
 
    // Process all suffixes one by one starting from
    // first suffix in txt[]
    for (int i=0; i<n; i++)
    {
        /* If the current suffix is at n-1, then we don’t
           have next substring to consider. So lcp is not
           defined for this substring, we put zero. */
        if (invSuff[i] == n-1)
        {
            k = 0;
            continue;
        }
 
        /* j contains index of the next substring to
           be considered  to compare with the present
           substring, i.e., next string in suffix array */
        int j = suffixArr[invSuff[i]+1];
 
        // Directly start matching from k'th index as
        // at-least k-1 characters will match
        while (i+k<n && j+k<n && txt[i+k]==txt[j+k])
            k++;
 
        lcp[invSuff[i]] = k; // lcp for the present suffix.
 
        // Deleting the starting character from the string.
        if (k>0)
            k--;
    }
 
    // return the constructed lcp array
    return lcp;
}
 
// A utility function to find longest common extension
// from index - L and index - R
int LCE(vector<int> lcp, vector<int>invSuff, int n,
        int L, int R)
{
    // Handle the corner case
    if (L == R)
        return (n-L);
 
    int low = minVal(invSuff[L], invSuff[R]);
    int high = maxVal(invSuff[L], invSuff[R]);
 
    int length = lcp[low];
 
    for (int i=low+1; i<high; i++)
    {
        if (lcp[i] < length)
            length = lcp[i];
    }
 
    return (length);
}
 
// A function to answer queries of longest common extension
void LCEQueries(string str, int n, Query q[],
                             int m)
{
    // Build a suffix array
    vector<int>suffixArr = buildSuffixArray(str, str.length());
 
    // An auxiliary array to store inverse of suffix array
    // elements. For example if suffixArr[0] is 5, the
    // invSuff[5] would store 0.  This is used to get next
    // suffix string from suffix array.
    vector<int> invSuff(n, 0);
 
    // Build a lcp vector
    vector<int>lcp = kasai(str, suffixArr, invSuff);
 
 
    for (int i=0; i<m; i++)
    {
        int L = q[i].L;
        int R = q[i].R;
 
        printf ("LCE (%d, %d) = %d\n", L, R,
                        LCE(lcp, invSuff, n, L, R));
    }
 
    return;
}
 
// Driver Program to test above functions
int main()
{
    string str = "abbababba";
    int n = str.length();
 
    // LCA Queries to answer
    Query q[] = {{1, 2}, {1, 6}, {0, 5}};
    int m = sizeof(q)/sizeof(q[0]);
 
    LCEQueries(str, n, q, m);
 
    return (0);
}
Producción

LCE (1, 2) = 1
LCE (1, 6) = 3
LCE (0, 5) = 4

Análisis de Reducción al método RMQ
Tiempo Complejidad: 

  • Para construir el lcp y la array de sufijos se necesita un tiempo O(N.logN) .
  • Para responder a cada consulta se necesita O(|invSuff[R] – invSuff[L]|) .

Por lo tanto, la complejidad temporal general es O(N.logN + Q. (|invSuff[R] – invSuff[L]|))
donde,
Q = Número de consultas LCE.
N = Longitud de la string de entrada.
invSuff[] = array de sufijos inversa de la string de entrada.
Aunque esto puede parecer un algoritmo ineficiente, este algoritmo generalmente supera a todos los demás algoritmos para responder a las consultas LCE.
Daremos una descripción detallada del rendimiento de este método en el siguiente conjunto.

Espacio auxiliar: Utilizamos el espacio auxiliar O(N) para almacenar arrays de sufijos lcp, sufijos y sufijos inversos.

Este artículo es una contribución de Rachit Belwariar . Si le gusta GeeksforGeeks y le gustaría contribuir, también puede escribir un artículo usando contribuya.geeksforgeeks.org o envíe su artículo por correo a contribuya@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *