大猩猩与杆子汇合点算法问题:无法通过测试用例求正确解法
分析与解法:大猩猩汇合问题
看起来你在处理这个竞赛编程题时,卡在了间接跳转的场景上——这确实是问题的核心难点,因为中转过程中杆子的出跳次数限制很容易被忽略,而且简单的“剩余容量统计”无法覆盖多步路径的情况。让我们一步步拆解问题,找出你的思路漏洞,然后给出正确的解决方案。
你的思路存在的核心问题
你提到的第三种情况(需要中转)的逻辑过于简略,主要有两个关键漏洞:
- 忽略中转杆子的出跳限制:当大猩猩通过中间杆子跳转时,中间杆子每让一只大猩猩跳走,就会消耗一次自己的
hi次数。比如,杆子B作为中转,10只大猩猩从A跳到B再到目标i,那么B的hi至少要≥10,否则无法完成中转。你的思路没有考虑这一点。 - 未建立路径可达性与流量分配模型:多步跳转的路径可能有很多条,需要确保所有需要中转的大猩猩都能找到一条可行的路径,且路径上的每个杆子的出跳次数足够承载经过的流量。简单统计“附近杆子剩余容量”无法处理这种复杂的流量分配。
正确的建模思路:网络流
这个问题本质上是一个带容量限制的多源汇网络流问题,我们可以通过以下方式建模:
节点拆分
将每根杆子u拆分为两个节点:
u_in:代表进入杆子u的流量(大猩猩到达u)u_out:代表从杆子u出去的流量(大猩猩从u跳走)
边的构建
- 源点S到各杆子的入节点:
- 对于目标杆子
i,源点S到i_in的边容量为0(因为i上的大猩猩不需要移动)。 - 对于其他杆子
u,源点S到u_in的边容量为gu(需要把u上的所有大猩猩运出去)。
- 对于目标杆子
- 杆子入节点到出节点:
- 每个
u_in到u_out的边容量为hi(最多允许hi只大猩猩从u跳走)。
- 每个
- 杆子出节点到其他杆子的入节点:
- 如果杆子
u和v的欧几里得距离≤J,那么u_out到v_in的边容量设为一个足够大的数(比如所有大猩猩的总数,因为最多所有大猩猩都经过这条边)。
- 如果杆子
- 目标杆子的入节点到汇点T:
i_in到汇点T的边容量为无穷大(可以接收任意多的大猩猩)。
计算最大流
计算从S到T的最大流,如果最大流等于所有非目标杆子的大猩猩总数(即sum(gu for u≠i)),说明所有大猩猩都能成功汇合到i。
Java实现示例
我们可以用Dinic算法来实现网络流,因为它在中等规模的图上效率很高。
第一步:实现Dinic算法的基础结构
import java.util.*; class Edge { int to, rev; double cap; Edge(int to, int rev, double cap) { this.to = to; this.rev = rev; this.cap = cap; } } class Dinic { List<List<Edge>> graph; int[] level, ptr; Dinic(int n) { graph = new ArrayList<>(); for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); level = new int[n]; ptr = new int[n]; } void addEdge(int from, int to, double cap) { graph.get(from).add(new Edge(to, graph.get(to).size(), cap)); graph.get(to).add(new Edge(from, graph.get(from).size()-1, 0)); } boolean bfs(int s, int t) { Arrays.fill(level, -1); level[s] = 0; Queue<Integer> q = new LinkedList<>(); q.add(s); while (!q.isEmpty()) { int u = q.poll(); for (Edge e : graph.get(u)) { if (e.cap > 1e-6 && level[e.to] == -1) { level[e.to] = level[u]+1; q.add(e.to); if (e.to == t) return true; } } } return false; } double dfs(int u, int t, double flow) { if (u == t) return flow; for (; ptr[u] < graph.get(u).size(); ptr[u]++) { Edge e = graph.get(u).get(ptr[u]); if (e.cap > 1e-6 && level[e.to] == level[u]+1) { double pushed = dfs(e.to, t, Math.min(flow, e.cap)); if (pushed > 1e-6) { e.cap -= pushed; graph.get(e.to).get(e.rev).cap += pushed; return pushed; } } } return 0; } double maxFlow(int s, int t) { double total = 0; while (bfs(s, t)) { Arrays.fill(ptr, 0); while (true) { double pushed = dfs(s, t, Double.MAX_VALUE); if (pushed < 1e-6) break; total += pushed; } } return total; } }
第二步:处理每个目标杆子的判断
public class GorillaCircus { static class Pole { double x, y; int g, h; Pole(double x, double y, int g, int h) { this.x = x; this.y = y; this.g = g; this.h = h; } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); double J = sc.nextDouble(); sc.nextLine(); List<Pole> poles = new ArrayList<>(); int totalGorillas = 0; for (int i = 0; i < N; i++) { double x = sc.nextDouble(); double y = sc.nextDouble(); int g = sc.nextInt(); int h = sc.nextInt(); poles.add(new Pole(x, y, g, h)); totalGorillas += g; } List<Integer> result = new ArrayList<>(); for (int target = 0; target < N; target++) { // 计算需要运输的大猩猩数量:总数减去目标杆子的数量 int needTransport = totalGorillas - poles.get(target).g; if (needTransport == 0) { // 没有大猩猩需要移动,直接加入结果 result.add(target); continue; } // 构建Dinic图:节点数=2*N + 2(S=2N, T=2N+1) int nodes = 2 * N + 2; Dinic dinic = new Dinic(nodes); int S = 2 * N; int T = 2 * N + 1; // 添加源点到各杆子入节点的边 for (int u = 0; u < N; u++) { if (u == target) { dinic.addEdge(S, u, 0); } else { dinic.addEdge(S, u, poles.get(u).g); } } // 添加各杆子入到出的边 for (int u = 0; u < N; u++) { dinic.addEdge(u, u + N, poles.get(u).h); } // 添加出节点到其他入节点的边 for (int u = 0; u < N; u++) { for (int v = 0; v < N; v++) { if (u == v) continue; double dx = poles.get(u).x - poles.get(v).x; double dy = poles.get(u).y - poles.get(v).y; double dist = Math.sqrt(dx*dx + dy*dy); if (dist <= J + 1e-6) { // 考虑浮点精度误差 dinic.addEdge(u + N, v, Double.MAX_VALUE); } } } // 添加目标入节点到汇点的边 dinic.addEdge(target, T, Double.MAX_VALUE); // 计算最大流 double flow = dinic.maxFlow(S, T); if (Math.abs(flow - needTransport) < 1e-6) { result.add(target); } } // 输出结果 if (result.isEmpty()) { System.out.println(-1); } else { for (int num : result) { System.out.print(num + " "); } System.out.println(); } } }
关键细节说明
- 浮点精度处理:比较距离和J时,要加上一个很小的epsilon(比如1e-6),避免因为浮点计算误差导致正确的距离被误判为超过J。
- 节点编号:每根杆子的入节点编号是
u,出节点是u+N,源点S是2N,汇点T是2N+1。 - Dinic算法中的容量:因为大猩猩数量是整数,但用double处理浮点精度问题更方便,判断流是否足够时用误差范围比较。
这样处理后,就能正确覆盖所有情况,包括直接跳转、多步中转,并且严格遵守每根杆子的出跳次数限制。
内容的提问来源于stack exchange,提问作者John Davis
相关产品推荐
相关产品推荐

