Processing math: 100%
(Analysis by Danny Mittal)

Define D to be the maximum value of Δj over all queries j and S to be the maximum value of Ti over all cities i.

Subtask 1: D200

By storing the sequence ci,0,,ci,Ti1 as an array for each city i, we can calculate the next city Bessie will go to each day in constant time given the current day t and her current city i by looking at index tmodTi in the array for i.

This means that we can answer each query in O(Δj) time, making this solution O(Ti+QD), which is fast enough here.

Subtask 2: N,Ti2103

Consider a situation where Bessie is currently at city i on day t, and we know i but only know tmodS. In order to figure out which city Bessie will go to next, we need to know t modulo Ti. Crucially, because Ti is a power of 2 for all i, and S is the maximum of all them, S will also be a power of 2 at least as large as Ti and will therefore be a multiple of Ti. This means that given tmodS we can simply mod by Ti to get tmodTi.

Thus, given i and tmodS, we know exactly what city Bessie will go to next. This suggests constructing a directed graph whose nodes are pairs (i,t) of a city i and a day t that we consider to be modulo S (so in particular, there are only S values of t). Each node has one outgoing edge pointing to another node (which makes this a functional graph, though we don't need to exploit this structure).

We now wish to be able to calculate which node we will get to if we follow the outgoing edge Δ times for large Δ. To do this, we can use binary lifting (construct a table storing for each node which node we will get to if we follow the outgoing edge 2k times for all 2kD, then use this table to answer each query in O(logD)).

There are NS nodes, and for each one we calculate O(logD) entries, making the construction O(NSlogD). Answering queries is then O(QlogD), making the overall runtime O((NS+Q)logD). S is bounded by Ti, and in this case is at most 210, making this fast enough.

Java code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class InfiniteAdventureN2 {
    public static final int LG = 60;
    public static final int S = 1024;

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tokenizer = new StringTokenizer(in.readLine());
        int n = Integer.parseInt(tokenizer.nextToken());
        int q = Integer.parseInt(tokenizer.nextToken());
        int[][] period = new int[n + 1][];
        tokenizer = new StringTokenizer(in.readLine());
        for (int city = 1; city <= n; city++) {
            period[city] = new int[Integer.parseInt(tokenizer.nextToken())];
        }
        for (int city = 1; city <= n; city++) {
            tokenizer = new StringTokenizer(in.readLine());
            for (int t = 0; t < period[city].length; t++) {
                period[city][t] = Integer.parseInt(tokenizer.nextToken());
            }
        }

        int[][][] binaryLift = new int[n + 1][S][LG];
        for (int city = 1; city <= n; city++) {
            for (int t = 0; t < S; t++) {
                binaryLift[city][t][0] = period[city][t % period[city].length];
            }
        }

        for (int k = 1; k < LG; k++) {
            for (int city = 1; city <= n; city++) {
                for (int t = 0; t < S; t++) {
                    int halfwayCity = binaryLift[city][t][k - 1];
                    int halfwayT = (int) ((t + (1L << (k - 1))) % S);
                    binaryLift[city][t][k] = binaryLift[halfwayCity][halfwayT][k - 1];
                }
            }
        }

        StringBuilder out = new StringBuilder();
        for (int j = 0; j < q; j++) {
            tokenizer = new StringTokenizer(in.readLine());
            int currentCity = Integer.parseInt(tokenizer.nextToken());
            long currentDay = Long.parseLong(tokenizer.nextToken());
            long daysAdventured = Long.parseLong(tokenizer.nextToken());

            for (int k = LG - 1; k >= 0; k--) {
                if ((daysAdventured & (1L << k)) != 0L) {
                    int t = (int) (currentDay % S);
                    currentCity = binaryLift[currentCity][t][k];
                    currentDay += 1L << k;
                }
            }

            out.append(currentCity).append('\n');
        }
        System.out.print(out);
    }
}

Subtask 3: N,Ti104

We want to do binary lifting like we did for Subtask 2, but a graph with NS nodes would now be too large. Therefore, instead of considering pairs of the form (i,t) where t is modulo S for all i, we will consider t to be modulo Ti, so that the number of pairs is equal to Ti105.

What goes wrong if we try to now just do binary lifting on these pairs? Consider a situation where we have two cities a and b such that Ta<Tb. Suppose that when Bessie is at city a and the day is t modulo Ta, Bessie goes next to city b. To calculate the binary lifting entry for Bessie going 21=2 steps from (a,t), we want to know where Bessie will go immediately after city b. However, we can't know this just from tmodTa, because that doesn't determine tmodTb.

This problem makes constructing a full binary lifting table impossible. To solve this, we will divide the cities into levels. At this point, there are two potential approaches:

Approach 1 (Level by Ti):

Each level contains all the cities with the same value of Ti.

For each city, we can at least construct the binary lifting table for that city up to the point where we reach a city of a higher level. If we also store the amount of days it takes for us to get to that first city of a higher level, then we can use the binary lifting table as follows:

To figure out where Bessie ends up if she starts at (i,t) and adventures for Δ days, we repeatedly do the following:

The second bullet point can be done at most lgD times, and because there are at most lgS levels, the first bullet point can be done at most lgS times in a row. This means that such a calculation takes O(lgDlgS) time.

We can first construct the binary lifting table by going in order of increasing powers of 2 and applying the above procedure to the existing entries (and also calculate when a higher level is reached where applicable), then use the same procedure to answer the queries. There are TilgD entries and Q queries, so the runtime complexity is

O((TilgD+Q)lgDlgS)
which is fast enough.

Approach 2 (sqrt decomposition):

We will split the nodes into small and large nodes, where small nodes have TiS and large nodes have Ti>S. For each large node i, we will compute the binary lifting table for all (i,t), but for each small node we will only compute the binary lifting table for (i,t) for 0t<S (i.e. for small nodes we only consider time mod S). In addition, for each small node we will store the min. time required to reach a big node, and only store the kth entry of the (i,t) table (corresponding to location after 2k steps) if after 2k steps we have never encountered a large node.

We can compute the small nodes in just O(NSlgD), For large nodes, to compute the kth entry, we first refer to the k1th entry. If this is a large node, we are immediately done, otherwise we jump to the next large node, and then recurse (roughly we are performing a query). This takes O(TiSlg2D).

To do a query, we do the following, for k=60,59,,0: If the next large node is within Δ away, jump to the next large node (and increment t and decrement Δ). Now, if Δ2k, jump to the next position in the binary lifting table (and modify t and Δ). This takes O(lgD) per query, so the total runtime is O(TiSlg2D+QlgD).

Java code for the first approach:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class InfiniteAdventure {
    public static final int LG = 60;

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tokenizer = new StringTokenizer(in.readLine());
        int n = Integer.parseInt(tokenizer.nextToken());
        int q = Integer.parseInt(tokenizer.nextToken());
        int[][] period = new int[n + 1][];
        tokenizer = new StringTokenizer(in.readLine());
        for (int city = 1; city <= n; city++) {
            period[city] = new int[Integer.parseInt(tokenizer.nextToken())];
        }
        for (int city = 1; city <= n; city++) {
            tokenizer = new StringTokenizer(in.readLine());
            for (int t = 0; t < period[city].length; t++) {
                period[city][t] = Integer.parseInt(tokenizer.nextToken());
            }
        }

        int[][][] binaryLift = new int[n + 1][][];
        int[][] firstHigher = new int[n + 1][];
        long[][] firstHigherDays = new long[n + 1][];
        for (int a = 1; a <= n; a++) {
            binaryLift[a] = new int[period[a].length][LG];
            firstHigherDays[a] = new long[period[a].length];
            firstHigher[a] = new int[period[a].length];
            for (int k = 0; k < period[a].length; k++) {
                if (period[period[a][k]].length > period[a].length) {
                    firstHigherDays[a][k] = 1;
                    firstHigher[a][k] = period[a][k];
                } else {
                    binaryLift[a][k][0] = period[a][k];
                }
            }
        }

        for (int k = 1; k < LG; k++) {
            for (int city = 1; city <= n; city++) {
                for (int t = 0; t < period[city].length; t++) {
                    if (firstHigherDays[city][t] == 0) {
                        int laterCity = binaryLift[city][t][k - 1];
                        long remainingDays = 1L << (k - 1);
                        int e = k - 1;
                        long currentDay = ((long) t) + (1L << (k - 1));
                        while (remainingDays > 0 && period[laterCity].length <= period[city].length) {
                            while ((1L << e) > remainingDays) {
                                e--;
                            }
                            int laterT = (int) (currentDay % period[laterCity].length);
                            if (firstHigherDays[laterCity][laterT] != 0 && firstHigherDays[laterCity][laterT] <= (1L << e)) {
                                remainingDays -= firstHigherDays[laterCity][laterT];
                                currentDay += firstHigherDays[laterCity][laterT];
                                laterCity = firstHigher[laterCity][laterT];
                            } else {
                                remainingDays -= 1L << e;
                                currentDay += 1L << e;
                                laterCity = binaryLift[laterCity][laterT][e];
                            }
                        }
                        if (period[laterCity].length > period[city].length) {
                            firstHigherDays[city][t] = (1L << k) - remainingDays;
                            firstHigher[city][t] = laterCity;
                        } else {
                            binaryLift[city][t][k] = laterCity;
                        }
                    }
                }
            }
        }

        StringBuilder out = new StringBuilder();
        for (int j = 0; j < q; j++) {
            tokenizer = new StringTokenizer(in.readLine());
            int currentCity = Integer.parseInt(tokenizer.nextToken());
            long currentDay = Long.parseLong(tokenizer.nextToken());
            long remainingDays = Long.parseLong(tokenizer.nextToken());

            int k = LG - 1;

            while (remainingDays > 0) {
                while ((1L << k) > remainingDays) {
                    k--;
                }
                int t = (int) (currentDay % period[currentCity].length);
                if (firstHigherDays[currentCity][t] != 0 && firstHigherDays[currentCity][t] <= (1L << k)) {
                    remainingDays -= firstHigherDays[currentCity][t];
                    currentDay += firstHigherDays[currentCity][t];
                    currentCity = firstHigher[currentCity][t];
                } else {
                    remainingDays -= 1L << k;
                    currentDay += 1L << k;
                    currentCity = binaryLift[currentCity][t][k];
                }
            }

            out.append(currentCity).append('\n');
        }
        System.out.print(out);
    }
}

Full solution

The bottleneck in the first approach to Subtask 3 is the fact that the amount of entries in the binary lifting table is already log times linear, and we then have to spend log2 on calculating each entry. In a normal binary lifting table, calculation of each entry is constant because you just look at two existing entries.

So, we want to make our binary lifting table more like a normal one. To do this, we will construct a binary lifting table for each level instead: the kth entry for (i,t) will, instead of telling you what city you will reach after 2k days, tell you the 2kth city you will reach that is at the same level as i. This can then be computed in constant time using previous entries.

To construct the base of the binary lifting table, we will need to know for each pair (i,t) what the soonest is that Bessie reaches a city j at the same level as i (and what j is), in addition to the same for Bessie reaching a higher level.

If Bessie starts from (i,t), let the first city she encounters of the same level be f(i,t) and the first city she encounters of the same level g(i,t). Before, we calculated g(i,t) as part of the binary lifting construction, but now f needs to be calculated beforehand, so we will calculate f and g together recursively:

From (i,t), check what city j Bessie will go to next.

This step thus takes O(TilogS) time.

Once those are calculated, we can calculate the binary lifting tables for each level, which will store not only the 2kth city in the same level that we get to but also the number of days needed to get there. The time taken is constant for each of the O(logD) entries per pair (i,t), so O(TilogD) overall.

To answer a query, we take a greedy strategy similar to in Subtask 3. Keeping track of our current city i and current day t, as well as the day that Bessie stops:

Each iteration of this process takes O(logS+logD) time, and each time we repeat it, after we increase our level as much as possible (the first bullet point) we end up at a lower level than last time, which means that we repeat the process at most O(logS) times. Thus, the entire calculation takes O(logS(logS+logD)) time.

The overall runtime is therefore

O(TilogS+TilogD+QlogS(logS+logD))=O((Ti+QlogS)(logS+logD))
which is fast enough.

Java code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class InfiniteAdventure {
    public static final int LG = 60;
    public static final long LIMIT = 1_000_000_000_000_000_000L;

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tokenizer = new StringTokenizer(in.readLine());
        int n = Integer.parseInt(tokenizer.nextToken());
        int q = Integer.parseInt(tokenizer.nextToken());
        int[][] period = new int[n + 1][];
        tokenizer = new StringTokenizer(in.readLine());
        for (int city = 1; city <= n; city++) {
            period[city] = new int[Integer.parseInt(tokenizer.nextToken())];
        }
        for (int city = 1; city <= n; city++) {
            tokenizer = new StringTokenizer(in.readLine());
            for (int t = 0; t < period[city].length; t++) {
                period[city][t] = Integer.parseInt(tokenizer.nextToken());
            }
        }

        int[][] firstHigher = new int[n + 1][];
        long[][] firstHigherDays = new long[n + 1][];
        int[][][] binaryLift = new int[n + 1][][];
        long[][][] binaryLiftDays = new long[n + 1][][];


        for (int a = 1; a <= n; a++) {
            firstHigher[a] = new int[period[a].length];
            firstHigherDays[a] = new long[period[a].length];
            binaryLift[a] = new int[period[a].length][LG];
            binaryLiftDays[a] = new long[period[a].length][LG];
        }

        new Object() {
            boolean[][] seen = new boolean[n + 1][];

            void perform() {
                for (int a = 1; a <= n; a++) {
                    seen[a] = new boolean[period[a].length];
                }
                for (int a = 1; a <= n; a++) {
                    for (int k = 0; k < period[a].length; k++) {
                        calc(a, k);
                    }
                }
            }

            void calc(int city, int t) {
                if (!seen[city][t]) {
                    seen[city][t] = true;

                    int laterCity = period[city][t];
                    long days = 1;
                    while (laterCity != 0 && period[laterCity].length <= period[city].length) {
                        int laterT = (int) ((t + days) % period[laterCity].length);
                        calc(laterCity, laterT);
                        if (period[laterCity].length == period[city].length) {
                            binaryLift[city][t][0] = laterCity;
                            binaryLiftDays[city][t][0] = days;
                        }
                        days += firstHigherDays[laterCity][laterT];
                        laterCity = firstHigher[laterCity][laterT];
                    }
                    if (laterCity != 0) {
                        firstHigher[city][t] = laterCity;
                        firstHigherDays[city][t] = days;
                    }
                }
            }
        }.perform();

        for (int k = 1; k < LG; k++) {
            for (int city = 1; city <= n; city++) {
                for (int t = 0; t < period[city].length; t++) {
                    if (firstHigher[city][t] == 0 || firstHigherDays[city][t] > (1L << k)) {
                        int halfwayCity = binaryLift[city][t][k - 1];
                        if (halfwayCity != 0) {
                            int halfwayT = (int) ((t + binaryLiftDays[city][t][k - 1]) % period[city].length);
                            long days = binaryLiftDays[city][t][k - 1] + binaryLiftDays[halfwayCity][halfwayT][k - 1];
                            if (binaryLift[halfwayCity][halfwayT][k - 1] != 0 && days <= LIMIT) {
                                binaryLift[city][t][k] = binaryLift[halfwayCity][halfwayT][k - 1];
                                binaryLiftDays[city][t][k] = days;
                            }
                        }
                    }
                }
            }
        }

        StringBuilder out = new StringBuilder();
        for (int j = 0; j < q; j++) {
            tokenizer = new StringTokenizer(in.readLine());
            int currentCity = Integer.parseInt(tokenizer.nextToken());
            long currentDay = Long.parseLong(tokenizer.nextToken());
            long remainingDays = Long.parseLong(tokenizer.nextToken());

            while (remainingDays > 0) {
                while (true) {
                    int t = (int) (currentDay % period[currentCity].length);
                    if (firstHigher[currentCity][t] == 0 || remainingDays < firstHigherDays[currentCity][t]) {
                        break;
                    }
                    currentDay += firstHigherDays[currentCity][t];
                    remainingDays -= firstHigherDays[currentCity][t];
                    currentCity = firstHigher[currentCity][t];
                }

                for (int k = LG - 1; k >= 0; k--) {
                    int t = (int) (currentDay % period[currentCity].length);
                    if (binaryLift[currentCity][t][k] != 0 && remainingDays >= binaryLiftDays[currentCity][t][k]) {
                        currentDay += binaryLiftDays[currentCity][t][k];
                        remainingDays -= binaryLiftDays[currentCity][t][k];
                        currentCity = binaryLift[currentCity][t][k];
                    }
                }
                if (remainingDays > 0) {
                    int t = (int) (currentDay % period[currentCity].length);
                    currentDay++;
                    remainingDays--;
                    currentCity = period[currentCity][t];
                }
            }

            out.append(currentCity).append('\n');
        }
        System.out.print(out);
    }
}