|
| 1 | +## Description |
| 2 | + |
| 3 | +In a graph G, if we can find a path from node x to node y, we say that x can reach y. Now you are given a directed graph G with n nodes and m edges. Besides, there are Q queries. Each query will contain two integers x and y. If x can reach y, print YES. Otherwise, print NO. |
| 4 | + |
| 5 | +Note: We guarantee there is at most one edge from node i to node j. |
| 6 | + |
| 7 | +### Input |
| 8 | + |
| 9 | +The first line will be an integer T (1 <= T <= 50). T is the number of test case. |
| 10 | + |
| 11 | +For each test case, the first line will be two integers n and m. (1 <= n <= 1000, 0 <= m <= min(n * n, 100000)) |
| 12 | + |
| 13 | +Then there will be m lines. Each line will have two integers x, y. "x y" means there is an edge from x to y. |
| 14 | + |
| 15 | +After that, there is an integer Q. (1 <= Q <= 500) The following are Q lines. Each line will have two integers x, y. |
| 16 | + |
| 17 | +All nodes are labeled from 1 to n. |
| 18 | + |
| 19 | +### Output |
| 20 | + |
| 21 | +For each query, if x can reach y, print YES. Otherwise, print NO. |
| 22 | + |
| 23 | +### Sample Input |
| 24 | + |
| 25 | +```log |
| 26 | +1 |
| 27 | +7 7 |
| 28 | +1 6 |
| 29 | +6 4 |
| 30 | +4 3 |
| 31 | +3 5 |
| 32 | +5 1 |
| 33 | +2 7 |
| 34 | +7 2 |
| 35 | +6 |
| 36 | +1 2 |
| 37 | +2 7 |
| 38 | +7 2 |
| 39 | +3 6 |
| 40 | +4 6 |
| 41 | +5 4 |
| 42 | +``` |
| 43 | + |
| 44 | +### Sample Output |
| 45 | + |
| 46 | +```log |
| 47 | +NO |
| 48 | +YES |
| 49 | +YES |
| 50 | +YES |
| 51 | +YES |
| 52 | +YES |
| 53 | +``` |
| 54 | + |
| 55 | +### HINT |
| 56 | + |
| 57 | +For the first sample, 1 cannot reach 2, because 2 and 7 form a ring. |
| 58 | + |
| 59 | +For the second sample, 2 can reach 7 directly. |
| 60 | + |
| 61 | +For the third sample, 3 -> 5 -> 1 -> 6 is one path. |
| 62 | + |
| 63 | +### 解答 (Solution) |
| 64 | + |
| 65 | +下面给出一种适用于本题约束的实现思路与要点说明(按读-处理-输出分离的风格),供参考与学习: |
| 66 | + |
| 67 | +1) 思路概述 |
| 68 | + |
| 69 | +- 由于题目给出的 n 上界为 1000,预计算所有点对的可达性是可行的。实现方法是对每个节点维护一个 BitSet 表示它能到达的所有节点,然后进行基于 BitSet 的传递闭包(Warshall-like)运算: |
| 70 | + - 初始化:对每个节点 i,令 `reach[i].set(i)`(节点可到达自身),并把所有有向边 u->v 设为 `reach[u].set(v)`。 |
| 71 | + - 传递闭包:按 k 从 0 到 n-1 遍历,对每个 i 如果 `reach[i].get(k)` 为真,则执行 `reach[i].or(reach[k])`。这一步使用 BitSet 的按字并行操作,速度远快于逐位循环。 |
| 72 | + - 回答查询:对于每个查询 (x,y) 只需检查 `reach[x].get(y)` 即可,时间 O(1)。 |
| 73 | + |
| 74 | +2) 读-处理-输出分离(实现结构) |
| 75 | + |
| 76 | +- `reader()`:使用快速读取(BufferedReader + StringTokenizer)读取 T、每个用例的 n、m、m 条边、Q 以及 Q 条查询;将节点索引转换为 0-based 并做基本的断言检查。 |
| 77 | +- `cal()`:构建 `BitSet[] reach` 并执行上述传递闭包,最后把每个查询的结果转换为 "YES" 或 "NO" 字符串并收集到结果列表。 |
| 78 | +- `output()`:一次性把所有结果行输出,保证每行以换行符结尾。 |
| 79 | + |
| 80 | +3) 复杂度分析 |
| 81 | + |
| 82 | +- 时间复杂度:三重循环的位运算实现,粗略为 O(n^3 / W) 的位操作(W 为机器字长,通常 64),实测在 n ≤ 1000 的约束下非常快。 |
| 83 | +- 空间复杂度:O(n^2) 位,即 O(n^2 / 8) 字节外加 BitSet 对象开销。对于 n ≤ 1000 是可接受的。 |
| 84 | + |
| 85 | +4) 边界与正确性要点 |
| 86 | + |
| 87 | +- 请确保在初始化时设置 `reach[i].set(i)`,使得查询 (x,x) 返回 YES(长度为 0 的路径)。 |
| 88 | +- 输入中的节点编号以 1..n 给出,内部实现要转换为 0-based 来索引数组与 BitSet。 |
| 89 | +- 对于非法边或查询索引(若存在),应做好范围检查以避免异常;本实现使用了 guard 条件来忽略越界边并在查询时返回 NO。 |
| 90 | + |
| 91 | +5) 可替代方案与优化 |
| 92 | + |
| 93 | +- 若 n 远大于 1000,则需要替代方法:例如对图先做强连通分量 (SCC) 压缩成 DAG,DAG 节点数通常远小于 n,再在 DAG 上做 BitSet 传递闭包或对每个查询做 BFS/DFS。 |
| 94 | +- SCC + DAG 方法适用于高密度或有大量互联的图。 |
| 95 | +- 若 Q 很小而 n 较大,则可以对每个查询直接做 BFS/DFS,复杂度为 O(Q*(n+m)),在 Q 很小的情形下更节省。 |
0 commit comments