Alphabetic Graph#12258

Alphabetic Graph (Editorial)

Problem Author:

turneja

Prepared by:

turneja

Edited by:

turneja

Solution

First, notice that the graph will consist of multiple connected components, each of which will be a directed tree with a cycle on the root and the non-cycle vertices pointing towards the cycle.

With DFS, we find the cycle and non-cycle vertices and for each non-cycle vertex we calculate the distance to the cycle and the first cycle vertex reachable from it.

Denote by f(u, k) the vertex we reach by starting from and following the outgoing edge of the current vertex times. The value of this function can be calculated quickly in \mathcal{O}(\log n) by building a binary lifting table of the graph.

Then we test reachability of from in the following way:

1. If two nodes and belong to different components then the answer is .

2. If is a cycle vertex and is a non-cycle vertex then the answer is .

3. If and are non-cycle vertices, then denote by dist_u and dist_v the distances from and to the cycle, respectively. Then if dist_u < dist_v or f(u, dist_u - dist_v) \neq v the answer is , otherwise is reachable from and the distance is dist_u - dist_v.

4. If is a cycle vertex then will be reachable from , and the distance can be calculated in the following way: assume is a cycle vertex. Then treat the cycle as a circular array with length and calculate the distance as (pos_v - pos_u + L) \mod L. If is not on the cycle then we denote as the first cycle vertex reachable from and calculate the distance as dist_u +(pos_v - pos_w + L) \mod L.

If the answer is not , first denote the distance from to as . To calculate the -th letter efficiently we augment the binary lifting table with additional information. For every vertex and power 2^j we precompute a block data[u][j] that summarizes the effect of traversing the next 2^j edges starting from . Each block stores:

: the endpoint f(u,2^j).

: the first letter on this segment.

: the last letter on this segment.

prefct: length (in edges) of the initial run of .

: length (in edges) of the final run of .

: number of edges in the segment (equals 2^j).

: whether all edges in the segment carry the same letter.

: total length produced by this segment alone (capped at a large sentinel ).

Notice that a run of consecutive edges with the same letter contributes 2^t - 1 characters to . Take care to for sufficiently large values of (e.g. t=50 since 2^{50}-1>10^{12}) clamp to to avoid overflow.

This setup allows us to write a merge function for two blocks that works in .

To extract the -th character, we greedily take the largest possible block starting from while keeping the produced size strictly below and the amount of edges traversed strictly below .

Then after this, consider the next single edge starting at . If merge(cur, data[u][0]).sz \geq k then the -th character equals the letter on that edge, otherwise even consuming all edges yields |S|<k and the answer is .

Time complexity: \mathcal{O}((n + q) \log n)

Memory complexity: \mathcal{O}(n \log n)

The details of the merge function are left in the implementation.

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