Алфавітний Граф#12258

Алфавітний Граф (Розбір)

Автор проблеми:

turneja

Підготовлено:

turneja

Редаговано:

turneja

Рішення

По-перше, зверніть увагу, що граф складатиметься з кількох зв'язаних компонентів, кожна з яких буде спрямованим деревом з циклом на корені, а нециклічні вершини вказуватимуть на цикл.

За допомогою DFS ми знаходимо цикл і нециклічні вершини, і для кожної нециклічної вершини обчислюємо відстань до циклу та першу вершину циклу, до якої можна дістатися з неї.

Позначимо f(u, k) як вершину, яку ми досягаємо, починаючи з і слідуючи за вихідною дугою поточної вершини разів. Значення цієї функції можна швидко обчислити за \mathcal{O}(\log n), побудувавши таблицю бінарного підйому графа.

Потім ми перевіряємо досяжність з наступним чином:

1. Якщо дві вершини і належать до різних компонентів, то відповідь - .

2. Якщо є вершиною циклу, а - нециклічною вершиною, то відповідь - .

3. Якщо і є нециклічними вершинами, то позначимо dist_u і dist_v відстані від і до циклу відповідно. Тоді, якщо dist_u < dist_v або f(u, dist_u - dist_v) \neq v, відповідь - , в іншому випадку досяжна з , а відстань дорівнює dist_u - dist_v.

4. Якщо є вершиною циклу, то буде досяжна з , і відстань можна обчислити наступним чином: припустимо, що є вершиною циклу. Тоді розглядаємо цикл як круговий масив довжини і обчислюємо відстань як (pos_v - pos_u + L) \mod L. Якщо не знаходиться на циклі, то позначимо як першу вершину циклу, до якої можна дістатися з , і обчислюємо відстань як dist_u +(pos_v - pos_w + L) \mod L.

Якщо відповідь не , спочатку позначимо відстань від до як . Щоб ефективно обчислити -ий символ, ми доповнюємо таблицю бінарного підйому додатковою інформацією. Для кожної вершини і степеня 2^j ми попередньо обчислюємо блок data[u][j], який узагальнює ефект проходження наступних 2^j дуз, починаючи з . Кожен блок зберігає:

: кінцева точка f(u,2^j).

: перша літера на цьому сегменті.

: остання літера на цьому сегменті.

prefct: довжина (в дугах) початкового пробігу .

: довжина (в дугах) фінального пробігу .

: кількість дуз у сегменті (дорівнює 2^j).

: чи всі дуги в сегменті несуть одну й ту ж літеру.

: загальна довжина , що виробляється цим сегментом (обмежена великим значенням ).

Зверніть увагу, що пробіг з послідовних дуз з однією й тією ж літерою вносить 2^t - 1 символів у . Будьте обережні, щоб для достатньо великих значень (наприклад, t=50, оскільки 2^{50}-1>10^{12}) обмежити до , щоб уникнути переповнення.

Ця налаштування дозволяє нам написати функцію злиття для двох блоків, яка працює за .

Щоб витягти -ий символ, ми жадібно беремо найбільший можливий блок, починаючи з , при цьому зберігаючи вироблений розмір строго нижче і кількість дуз, що проходять, строго нижче .

Потім після цього розглядаємо наступну одиничну дугу, що починається з . Якщо merge(cur, data[u][0]).sz \geq k, то -ий символ дорівнює літері на цій дузі, в іншому випадку навіть споживши всі дуз, отримаємо |S|<k і відповідь - .

Часова складність: \mathcal{O}((n + q) \log n)

Пам'яттєва складність: \mathcal{O}(n \log n)

Деталі функції злиття залишені в реалізації.

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

using namespace std;
using namespace __gnu_pbds;

#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;
}