You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

大猩猩与杆子汇合点算法问题:无法通过测试用例求正确解法

分析与解法:大猩猩汇合问题

看起来你在处理这个竞赛编程题时,卡在了间接跳转的场景上——这确实是问题的核心难点,因为中转过程中杆子的出跳次数限制很容易被忽略,而且简单的“剩余容量统计”无法覆盖多步路径的情况。让我们一步步拆解问题,找出你的思路漏洞,然后给出正确的解决方案。

你的思路存在的核心问题

你提到的第三种情况(需要中转)的逻辑过于简略,主要有两个关键漏洞:

  1. 忽略中转杆子的出跳限制:当大猩猩通过中间杆子跳转时,中间杆子每让一只大猩猩跳走,就会消耗一次自己的hi次数。比如,杆子B作为中转,10只大猩猩从A跳到B再到目标i,那么B的hi至少要≥10,否则无法完成中转。你的思路没有考虑这一点。
  2. 未建立路径可达性与流量分配模型:多步跳转的路径可能有很多条,需要确保所有需要中转的大猩猩都能找到一条可行的路径,且路径上的每个杆子的出跳次数足够承载经过的流量。简单统计“附近杆子剩余容量”无法处理这种复杂的流量分配。

正确的建模思路:网络流

这个问题本质上是一个带容量限制的多源汇网络流问题,我们可以通过以下方式建模:

节点拆分

将每根杆子u拆分为两个节点:

  • u_in:代表进入杆子u的流量(大猩猩到达u)
  • u_out:代表从杆子u出去的流量(大猩猩从u跳走)

边的构建

  1. 源点S到各杆子的入节点:
    • 对于目标杆子i,源点S到i_in的边容量为0(因为i上的大猩猩不需要移动)。
    • 对于其他杆子u,源点S到u_in的边容量为gu(需要把u上的所有大猩猩运出去)。
  2. 杆子入节点到出节点:
    • 每个u_in到u_out的边容量为hi(最多允许hi只大猩猩从u跳走)。
  3. 杆子出节点到其他杆子的入节点:
    • 如果杆子u和v的欧几里得距离≤J,那么u_out到v_in的边容量设为一个足够大的数(比如所有大猩猩的总数,因为最多所有大猩猩都经过这条边)。
  4. 目标杆子的入节点到汇点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();
        }
    }
}

关键细节说明

  1. 浮点精度处理:比较距离和J时,要加上一个很小的epsilon(比如1e-6),避免因为浮点计算误差导致正确的距离被误判为超过J。
  2. 节点编号:每根杆子的入节点编号是u,出节点是u+N,源点S是2N,汇点T是2N+1。
  3. Dinic算法中的容量:因为大猩猩数量是整数,但用double处理浮点精度问题更方便,判断流是否足够时用误差范围比较。

这样处理后,就能正确覆盖所有情况,包括直接跳转、多步中转,并且严格遵守每根杆子的出跳次数限制。

内容的提问来源于stack exchange,提问作者John Davis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:33:44