Grafo Alfabético#12258

Grafo Alfabético (Editorial)

Autor del Problema:

turneja

Preparado por:

turneja

Editado por:

turneja

Solución

Primero, observa que el grafo consistirá en múltiples componentes conectados, cada uno de los cuales será un árbol dirigido con un ciclo en la raíz y los vértices no cíclicos apuntando hacia el ciclo.

Con DFS, encontramos el ciclo y los vértices no cíclicos y para cada vértice no cíclico calculamos la distancia al ciclo y el primer vértice del ciclo alcanzable desde él.

Denotemos por f(u, k) el vértice que alcanzamos comenzando desde y siguiendo la arista saliente del vértice actual veces. El valor de esta función se puede calcular rápidamente en \mathcal{O}(\log n) construyendo una tabla de levantamiento binario del grafo.

Luego probamos la alcanzabilidad de desde de la siguiente manera:

1. Si dos nodos y pertenecen a diferentes componentes, entonces la respuesta es .

2. Si es un vértice cíclico y es un vértice no cíclico, entonces la respuesta es .

3. Si y son vértices no cíclicos, entonces denotemos por dist_u y dist_v las distancias desde y al ciclo, respectivamente. Entonces, si dist_u < dist_v o f(u, dist_u - dist_v) \neq v, la respuesta es , de lo contrario, es alcanzable desde y la distancia es dist_u - dist_v.

4. Si es un vértice cíclico, entonces será alcanzable desde , y la distancia se puede calcular de la siguiente manera: supongamos que es un vértice cíclico. Entonces trata el ciclo como un arreglo circular de longitud y calcula la distancia como (pos_v - pos_u + L) \mod L. Si no está en el ciclo, entonces denotamos como el primer vértice cíclico alcanzable desde y calculamos la distancia como dist_u +(pos_v - pos_w + L) \mod L.

Si la respuesta no es , primero denotamos la distancia desde hasta como . Para calcular la -ésima letra de manera eficiente, aumentamos la tabla de levantamiento binario con información adicional. Para cada vértice y potencia 2^j, precomputamos un bloque data[u][j] que resume el efecto de recorrer las siguientes 2^j aristas comenzando desde . Cada bloque almacena:

: el punto final f(u,2^j).

: la primera letra en este segmento.

: la última letra en este segmento.

prefct: longitud (en aristas) de la ejecución inicial de .

: longitud (en aristas) de la ejecución final de .

: número de aristas en el segmento (igual a 2^j).

: si todas las aristas en el segmento llevan la misma letra.

: longitud total producida por este segmento solo (limitada a un gran centinela ).

Observa que una ejecución de aristas consecutivas con la misma letra contribuye con 2^t - 1 caracteres a . Ten cuidado de para valores suficientemente grandes de (por ejemplo, t=50 ya que 2^{50}-1>10^{12}) limitar a para evitar desbordamientos.

Esta configuración nos permite escribir una función de combinación para dos bloques que funciona en .

Para extraer el carácter -ésimo, tomamos de manera codiciosa el bloque más grande posible comenzando desde mientras mantenemos el tamaño producido estrictamente por debajo de y la cantidad de aristas recorridas estrictamente por debajo de .

Luego, después de esto, considera la siguiente arista única comenzando en . Si merge(cur, data[u][0]).sz \geq k entonces el carácter -ésimo es igual a la letra en esa arista, de lo contrario, incluso consumiendo todas las aristas da como resultado |S|<k y la respuesta es .

Complejidad de tiempo: \mathcal{O}((n + q) \log n)

Complejidad de memoria: \mathcal{O}(n \log n)

Los detalles de la función de combinación se dejan en la implementación.

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pb_ds;

#define endl "\n"
#define ll long long
#define IOS ios_base::sync_with_stdio(false); cin.tie(nullptr);

const int N = 2e5 + 5;
const int K = 19;
const ll INF = 1e18;

int up[K][N];
int color[N];
int group[N];
bool cycle[N];
int pos[N];
int entry[N];
int sz[N];
int dist[N];
int parent[N];
char wt[N];
ll pw[50];

void dfs(int u, int p) {

    color[u] = 1;
    parent[u] = p;
    int v = up[0][u];
    if (color[v] == 0) {
        dfs(v, u);
    } else if (color[v] == 1) {
        int x = u;
        vector<int> cyc;
        while (x != v) {
            cyc.push_back(x);
            x = parent[x];
        }
        cyc.push_back(v);
        reverse(cyc.begin(), cyc.end());
        for (int i = 0; i < (int)cyc.size(); i++) {
            int x = cyc[i];
            cycle[x] = true;
            pos[x] = i;
            dist[x] = 0;
            sz[x] = cyc.size();
            group[x] = v;
        }
    }
    color[u] = 2;
    if (!cycle[u]) {
        group[u] = group[v];
        if (cycle[v]) {
            dist[u] = 1;
            entry[u] = v;
        } else {
            dist[u] = dist[v] + 1;
            entry[u] = entry[v];
        }
    }
}

struct Data {
    char pref;
    char suf;
    int prefct;
    int sufct;
    int len;
    bool entire;
    ll sz;

    Data () {}

    Data(char c) {
        pref = c;
        suf = c;
        prefct = 1;
        sufct = 1;
        len = 1;
        entire = true;
        sz = 1;
    }
};

Data combine(Data left, Data right) {
    Data cur;
    cur.pref = left.pref;
    cur.suf = right.suf;
    if (left.entire && left.pref == right.pref) {
        cur.prefct = left.len + right.prefct;
    } else {
        cur.prefct = left.prefct;
    }
    if (right.entire && left.suf == right.pref) {
        cur.sufct = left.sufct + right.len;
    } else {
        cur.sufct = right.sufct;
    }

    cur.len = left.len + right.len;
    cur.entire = left.entire && right.entire && left.pref == right.pref;
    if (left.sz >= INF || right.sz >= INF) {
        cur.sz = INF;
    } else {
        cur.sz = left.sz + right.sz;
        if (left.suf == right.pref) {
            cur.sz -= pw[left.sufct];
            cur.sz -= pw[right.prefct];
            cur.sz += (left.sufct + right.prefct >= 50 ? INF : pw[left.sufct + right.prefct]);
        }
        cur.sz = min(cur.sz, INF);
    }
    return cur;
}

Data dat[K][N];

int kth(int u, int k) {
    for (int i = K - 1; i >= 0; i--) {
        int c = k & (1 << i);
        if (c) {
            u = up[i][u];
        }
    }
    return u;
}

int main() {
    IOS;
    pw[0] = 0;
    for (int i = 1; i < 50; i++) {
        pw[i] = ((pw[i - 1] + 1) * 2) - 1;
        pw[i] = min(pw[i], INF);
    }
    int n, q;
    cin >> n >> q;
    for (int i = 0; i < n; i++) {
        int j; char c;
        cin >> j >> c;
        j--;
        up[0][i] = j;
        dat[0][i] = Data(c);
        wt[i] = c;
    }
    for (int k = 1; k < K; k++) {
        for (int i = 0; i < n; i++) {
            up[k][i] = up[k - 1][up[k - 1][i]];
            dat[k][i] = combine(dat[k - 1][i], dat[k - 1][up[k - 1][i]]);
        }
    }
    for (int i = 0; i < n; i++) {
        if (color[i] == 0) {
            dfs(i, i);
        }
    }
    for (int i = 0; i < q; i++) {
        int u, v; ll k;
        cin >> u >> v >> k;
        u--, v--;
        if (group[u] != group[v] || (cycle[u] && !cycle[v]) || u == v) {
            cout << -1 << endl;
            continue;
        }
        int d = 0;
        if (!cycle[u] && !cycle[v]) {
            int k = dist[u] - dist[v];
            if (k < 0) {
                cout << -1 << endl;
                continue;
            }
            int vc = kth(u, k);
            if (vc != v) {
                cout << -1 << endl;
                continue;
            }
            d = k;
        } else {
            int U = u;
            if (!cycle[u]) {
                d += dist[u];
                U = entry[u];
            }
            if (pos[U] <= pos[v]) {
                d += pos[v] - pos[U];
            } else {
                d += sz[U] - pos[U] + pos[v];
            }
        }
        if (k == 1) {
            cout << wt[u] << endl;
            continue;
        }
        if (d == 1) {
            cout << -1 << endl;
            continue;
        }
        Data cur = dat[0][u];
        u = up[0][u];
        for (int j = K - 1; j >= 0; j--) {
            Data nx = combine(cur, dat[j][u]);
            if (nx.sz < k && nx.len < d) {
                cur = nx;
                u = up[j][u];
            }
        }
        Data tmp = Data(wt[u]);
        cur = combine(cur, tmp);
        if (cur.sz < k) {
            cout << -1 << endl;
        } else {
            cout << wt[u] << endl;
        }
    }
    return 0;
}