题目描述
对于一个城市来说,排水系统是极其重要的一个部分。
有一天,小 C 拿到了某座城市排水系统的设计图。排水系统由 n 个排水结点(它们从 1∼n 编号)和若干个单向排水管道构成。每一个排水结点有若干个管道用于汇集其他排水结点的污水(简称为该结点的汇集管道),也有若干个管道向其他的排水结点排出污水(简称为该结点的排出管道)。
排水系统的结点中有 m 个污水接收口,它们的编号分别为 1,2,…,m,污水只能从这些接收口流入排水系统,并且这些结点没有汇集管道。排水系统中还有若干个最终排水口,它们将污水运送到污水处理厂,没有排出管道的结点便可视为一个最终排水口。
现在各个污水接收口分别都接收了 1 吨污水,污水进入每个结点后,会均等地从当前结点的每一个排出管道流向其他排水结点,而最终排水口将把污水排出系统。
现在小 C 想知道,在该城市的排水系统中,每个最终排水口会排出多少污水。该城市的排水系统设计科学,管道不会形成回路,即不会发生污水形成环流的情况。
输入描述
第一行两个用单个空格分隔的整数 n,m。分别表示排水结点数与接收口数量。
接下来 n 行,第 i 行用于描述结点 i 的所有排出管道。其中每行第一个整数 di 表示其排出管道的数量,接下来 di个用单个空格分隔的整数 a1,a2,…,adi 依次表示管道的目标排水结点。 保证不会出现两条起始结点与目标结点均相同的管道。
其中,1 ≤ n ≤ 10^5,1 ≤ m ≤ 10,0 ≤ di ≤ 5。
数据保证,污水在从一个接收口流向一个最终排水口的过程中,不会经过超过 1010 个中间排水结点(即接收口和最终排水口不算在内)。
输出描述
输出若干行,按照编号从小到大的顺序,给出每个最终排水口排出的污水体积。其中体积使用分数形式进行输出,即每行输出两个用单个空格分隔的整数 p,q,表示排出的污水体积为 。要求 p 与 q 互素,q=1 时也需要输出 q。
输入输出样例
示例 1
输入
5 1
3 2 3 5
2 4 5
2 5 4
0
0
输出
1 3
2 3
解题思路
本题是有向无环图的拓扑排序,而拓扑排序其实就是在图上的搜索,本质上就是dfs和bfs。
这题是典型的有起点有终点的有向图,我们自然而然的想到使用bfs进行遍历,一般像这样的题我们有以下需要关注的点:
- 使用邻接表存储点很多的稀疏图;使用ru和chu两个长度为n的数组记录每个点的入度和出度;使用链式队列完成bfs等。
- 通常我们可以开辟dp数组记录节点状态,但是本题比较特殊,对于每个点的状态使用p和q组合的一个分数来表示,这样我们就可以利用p和q数组代替dp数组记录节点状态了。
这道题需要我们对分数除法、分数加法、分数约分的过程进行模拟,其内容笔者使用了一个addWater方法进行操作,调用了最大公约数gcd和最小公倍数lcm算法,属于算法有关基础数学知识的内容。
这里需要注意的是,由于分数可能很大,所以我们不仅需要使用long类型进行存储计算,还要对求最小公倍数的时候先除以最大公因数再乘第二个数,否则有可能溢出。
代码题解
具体的代码并没有难以理解的地方,以下是具体的代码:
import java.io.*;
import java.util.*;
public class Main {
static int n, m;
static ArrayList<ArrayList<Integer>> edges;
static int[] ru, chu;
static long[] p, q;
static Queue<Integer> qu;
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
String[] temp = in.readLine().split(" ");
n = Integer.parseInt(temp[0]);
m = Integer.parseInt(temp[1]);
init();
for (int i = 1; i <= n; i++) {
temp = in.readLine().split(" ");
int t = Integer.parseInt(temp[0]);
chu[i] = t;
for (int j = 1; j <= t; j++) {
int next = Integer.parseInt(temp[j]);
edges.get(i).add(next);
ru[next]++;
}
}
for (int i = 1; i <= m; i++) {
qu.add(i);
p[i] = 1;
q[i] = 1;
}
while (!qu.isEmpty()) {
int t = qu.poll();
for (int e : edges.get(t)) {
addWater(t, e, chu[t]);
ru[e]--;
if (ru[e] == 0 && chu[e] > 0) {
qu.add(e);
}
}
p[t] = 0;
q[t] = 0;
}
for (int i = 1; i <= n; i++) {
if (p[i] != 0) {
out.print(p[i]);
out.print(" ");
out.print(q[i]);
out.print("\n");
}
}
out.flush();
}
public static void addWater(int sourse, int target, int num) {
long p1 = p[sourse];
long q1 = q[sourse];
long t = gcd(p1, num);
p1 /= t;
q1 *= num / t;
long p2 = p[target];
long q2 = q[target];
if (p2 == 0) {
p[target] = p1;
q[target] = q1;
} else {
long x = lcm(q1, q2);
long t1 = x / q1 * p1 + x / q2 * p2;
long t2 = x;
long tt = gcd(t1, t2);
p[target] = t1 / tt;
q[target] = t2 / tt;
}
}
public static long gcd(long a, long b) {
return a % b == 0 ? b : gcd(b, a % b);
}
public static long lcm(long a, long b) {
return a / gcd(a, b) * b;
}
public static void init() {
edges = new ArrayList<>();
for (int i = 0; i <= n; i++) {
edges.add(new ArrayList<>());
}
p = new long[n + 1];
q = new long[n + 1];
ru = new int[n + 1];
chu = new int[n + 1];
qu = new LinkedList<>();
}
}