Phoenix and Odometers (Codeforces 1515G)
Tags: Math, dfs and similar

In Fire City, there are $$$n$$$ intersections and $$$m$$$ one-way roads. The $$$i$$$-th road goes from intersection $$$a_i$$$ to $$$b_i$$$ and has length $$$l_i$$$ miles. There are $$$q$$$ cars that may only drive along those roads. The $$$i$$$-th car starts at intersection $$$v_i$$$ and has an odometer that begins at $$$s_i$$$, increments for each mile driven, and resets to $$$0$$$ whenever it reaches $$$t_i$$$. Phoenix has been tasked to drive cars along some roads (possibly none) and return them to their initial intersection with the odometer showing $$$0$$$.For each car, please find if this is possible. A car may visit the same road or intersection an arbitrary number of times. The odometers don't stop counting the distance after resetting, so odometers may also be reset an arbitrary number of times.

Input: The first line of the input contains two integers $$$n$$$ and $$$m$$$ ($$$2 \le n \le 2 \cdot 10^5$$$; $$$1 \le m \le 2 \cdot 10^5$$$) — the number of intersections and the number of roads, respectively.Each of the next $$$m$$$ lines contain three integers $$$a_i$$$, $$$b_i$$$, and $$$l_i$$$ ($$$1 \le a_i, b_i \le n$$$; $$$a_i \neq b_i$$$; $$$1 \le l_i \le 10^9$$$) — the information about the $$$i$$$-th road. The graph is not necessarily connected. It is guaranteed that between any two intersections, there is at most one road for each direction.The next line contains an integer $$$q$$$ ($$$1 \le q \le 2 \cdot 10^5$$$) — the number of cars.Each of the next $$$q$$$ lines contains three integers $$$v_i$$$, $$$s_i$$$, and $$$t_i$$$ ($$$1 \le v_i \le n$$$; $$$0 \le s_i < t_i \le 10^9$$$) — the initial intersection of the $$$i$$$-th car, the initial number on the $$$i$$$-th odometer, and the number at which the $$$i$$$-th odometer resets, respectively.

Output: Print $$$q$$$ answers. If the $$$i$$$-th car's odometer may be reset to $$$0$$$ by driving through some roads (possibly none) and returning to its starting intersection $$$v_i$$$, print YES. Otherwise, print NO.

Note: The illustration for the first example is below:  In the first query, Phoenix can drive through the following cities: $$$1$$$ $$$\rightarrow$$$ $$$2$$$ $$$\rightarrow$$$ $$$3$$$ $$$\rightarrow$$$ $$$1$$$ $$$\rightarrow$$$ $$$2$$$ $$$\rightarrow$$$ $$$3$$$ $$$\rightarrow$$$ $$$1$$$. The odometer will have reset $$$3$$$ times, but it displays $$$0$$$ at the end.In the second query, we can show that there is no way to reset the odometer to $$$0$$$ and return to intersection $$$1$$$.In the third query, the odometer already displays $$$0$$$, so there is no need to drive through any roads.Below is the illustration for the second example:   

Sample Inputs:
4 4
1 2 1
2 3 1
3 1 2
1 4 3
3
1 1 3
1 2 4
4 0 1

4 5
1 2 1
2 3 1
3 1 2
1 4 1
4 3 2
2
1 2 4
4 3 5

Sample Outputs:
YES
NO
YES

YES
YES

Solution (Python):
import sys
import io, os
input = io.BytesIO(os.read(0, os.fstat(0).st_size)).readline
from math import gcd
(n, m) = map(int, input().split())
E = [[] for i in range(n)]
X = [[] for i in range(n)]
for i in range(m):
    (a, b, x) = map(int, input().split())
    a -= 1
    b -= 1
    E[a].append((b, x))
    X[a].append(b)

def scc(E):
    n = len(E)
    iE = [[] for _ in range(n)]
    for (i, e) in enumerate(E):
        for v in e:
            iE[v].append(i)
    T = []
    done = [0] * n
    ct = 0
    for i0 in range(n):
        if done[i0]:
            continue
        Q = [~i0, i0]
        while Q:
            i = Q.pop()
            if i < 0:
                if done[~i] == 2:
                    continue
                done[~i] = 2
                T.append(~i)
                ct += 1
                continue
            if i >= 0:
                if done[i]:
                    continue
                done[i] = 1
            for j in E[i]:
                if done[j]:
                    continue
                Q.append(~j)
                Q.append(j)
    done = [0] * n
    SCC = []
    for i0 in T[::-1]:
        if done[i0]:
            continue
        L = []
        Q = [~i0, i0]
        while Q:
            i = Q.pop()
            if i < 0:
                if done[~i] == 2:
                    continue
                done[~i] = 2
                L.append(~i)
                continue
            if i >= 0:
                if done[i]:
                    continue
                done[i] = 1
            for j in iE[i]:
                if done[j]:
                    continue
                Q.append(~j)
                Q.append(j)
        SCC.append(L)
    return SCC
S = scc(X)
ANS = [0] * len(S)
DIS = [-1] * n
SCLIST = [-1] * n
for i in range(len(S)):
    scc = S[i]
    for node in scc:
        SCLIST[node] = i
    if len(S[i]) == 1:
        continue
    Q = [S[i][0]]
    DIS[S[i][0]] = 0
    while Q:
        x = Q.pop()
        for (to, cost) in E[x]:
            if SCLIST[to] != i:
                continue
            if DIS[to] == -1:
                DIS[to] = DIS[x] + cost
                Q.append(to)
            else:
                ANS[i] = gcd(ANS[i], DIS[to] - (DIS[x] + cost))
q = int(input())
ALIST = [''] * q
for queries in range(q):
    (v, s, t) = map(int, input().split())
    if s == 0:
        ALIST[queries] = 'YES'
        continue
    x = ANS[SCLIST[v - 1]]
    y = gcd(x, t)
    if (t - s) % y == 0:
        ALIST[queries] = 'YES'
    else:
        ALIST[queries] = 'NO'
sys.stdout.write('\n'.join(ALIST))

Solution (Python):
import sys
import io, os
input = io.BytesIO(os.read(0, os.fstat(0).st_size)).readline
from math import gcd
(n, m) = map(int, input().split())
E = [[] for i in range(n)]
X = [[] for i in range(n)]
for i in range(m):
    (a, b, x) = map(int, input().split())
    a -= 1
    b -= 1
    E[a].append((b, x))
    X[a].append(b)

def scc(E):
    n = len(E)
    iE = [[] for _ in range(n)]
    for (i, e) in enumerate(E):
        for v in e:
            iE[v].append(i)
    T = []
    done = [0] * n
    ct = 0
    for i0 in range(n):
        if done[i0]:
            continue
        Q = [~i0, i0]
        while Q:
            i = Q.pop()
            if i < 0:
                if done[~i] == 2:
                    continue
                done[~i] = 2
                T.append(~i)
                ct += 1
                continue
            if i >= 0:
                if done[i]:
                    continue
                done[i] = 1
            for j in E[i]:
                if done[j]:
                    continue
                Q.append(~j)
                Q.append(j)
    done = [0] * n
    SCC = []
    for i0 in T[::-1]:
        if done[i0]:
            continue
        L = []
        Q = [~i0, i0]
        while Q:
            i = Q.pop()
            if i < 0:
                if done[~i] == 2:
                    continue
                done[~i] = 2
                L.append(~i)
                continue
            if i >= 0:
                if done[i]:
                    continue
                done[i] = 1
            for j in iE[i]:
                if done[j]:
                    continue
                Q.append(~j)
                Q.append(j)
        SCC.append(L)
    return SCC
S = scc(X)
ANS = [0] * len(S)
DIS = [-1] * n
SCLIST = [-1] * n
for i in range(len(S)):
    scc = S[i]
    for node in scc:
        SCLIST[node] = i
    if len(S[i]) == 1:
        continue
    Q = [S[i][0]]
    DIS[S[i][0]] = 0
    while Q:
        x = Q.pop()
        for (to, cost) in E[x]:
            if SCLIST[to] != i:
                continue
            if DIS[to] == -1:
                DIS[to] = DIS[x] + cost
                Q.append(to)
            else:
                ANS[i] = gcd(ANS[i], DIS[to] - (DIS[x] + cost))
q = int(input())
ALIST = [''] * q
for queries in range(q):
    (v, s, t) = map(int, input().split())
    if s == 0:
        ALIST[queries] = 'YES'
        continue
    x = ANS[SCLIST[v - 1]]
    y = gcd(x, t)
    if (t - s) % y == 0:
        ALIST[queries] = 'YES'
    else:
        ALIST[queries] = 'NO'
sys.stdout.write('\n'.join(ALIST))

Solution (Python):
import os
import sys
from io import BytesIO, IOBase
''

def find_SCC(graph):
    (SCC, S, P) = ([], [], [])
    depth = [0] * len(graph)
    stack = list(range(len(graph)))
    while stack:
        node = stack.pop()
        if node < 0:
            d = depth[~node] - 1
            if P[-1] > d:
                SCC.append(S[d:])
                del S[d:], P[-1]
                for node in SCC[-1]:
                    depth[node] = -1
        elif depth[node] > 0:
            while P[-1] > depth[node]:
                P.pop()
        elif depth[node] == 0:
            S.append(node)
            P.append(len(S))
            depth[node] = len(S)
            stack.append(~node)
            stack += graph[node]
    return SCC[::-1]

def main():
    from math import gcd
    (n, m) = map(int, input().split())
    graph = []
    graphSCC = []
    for _ in range(n):
        graph.append([])
        graphSCC.append([])
    for _ in range(m):
        (a, b, l) = map(int, input().split())
        graphSCC[a - 1].append(b - 1)
        graph[a - 1].append((b - 1, l))
    scc = find_SCC(graphSCC)
    sccNum = [0] * n
    for i in range(len(scc)):
        for elem in scc[i]:
            sccNum[elem] = i
    weight = [-1] * n
    gcdd = [0] * len(scc)
    for i in range(len(scc)):
        stack = [scc[i][0]]
        weight[scc[i][0]] = 0
        while stack:
            cur = stack.pop()
            for elem in graph[cur]:
                if sccNum[elem[0]] != i:
                    continue
                if weight[elem[0]] == -1:
                    stack.append(elem[0])
                    weight[elem[0]] = weight[cur] + elem[1]
                else:
                    gcdd[i] = gcd(gcdd[i], -weight[elem[0]] + weight[cur] + elem[1])
    q = int(input())
    for _ in range(q):
        (v, s, t) = map(int, input().split())
        if s % gcd(gcdd[sccNum[v - 1]], t):
            print('NO')
        else:
            print('YES')
BUFSIZE = 8192

class FastIO(IOBase):
    newlines = 0

    def __init__(self, file):
        self._fd = file.fileno()
        self.buffer = BytesIO()
        self.writable = 'x' in file.mode or 'r' not in file.mode
        self.write = self.buffer.write if self.writable else None

    def read(self):
        while True:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            if not b:
                break
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines = 0
        return self.buffer.read()

    def readline(self):
        while self.newlines == 0:
            b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
            self.newlines = b.count(b'\n') + (not b)
            ptr = self.buffer.tell()
            (self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
        self.newlines -= 1
        return self.buffer.readline()

    def flush(self):
        if self.writable:
            os.write(self._fd, self.buffer.getvalue())
            (self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

    def __init__(self, file):
        self.buffer = FastIO(file)
        self.flush = self.buffer.flush
        self.writable = self.buffer.writable
        self.write = lambda s: self.buffer.write(s.encode('ascii'))
        self.read = lambda : self.buffer.read().decode('ascii')
        self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')
if __name__ == '__main__':
    main()