九条可怜最近接触到了攀岩这项运动。作为一名运动量接近为零的家里蹲,相比于动手攀岩,可怜显然更加享受攀岩运动中动脑的部分,即对攀岩路线的规划。
为了只动脑,可怜设计了一个简易的攀岩机器人来帮她动手。这个机器人由一个核心(大小可以忽略不计)和三个长度至多为 r 的可伸缩机械臂组成,如下图所示:

robot.png

一面攀岩墙可以看成一个二维的无穷平面,上面有 n 个不同位置的岩点 p1​,…,pn​。在攀岩开始时,机器人的两个机械臂需要分别抓住岩点 p1​ 和 p2​,而其核心和第三个机械臂可以在任意位置。攀岩的目标是让机器人有两支机械臂同时抓握住岩点 pn​。
在攀岩过程中,机器人需要保证每时每刻都有两支机械臂抓握住两个不同的岩点。在满足这点的情况下,机器人可以在机械臂长度允许的情况下进行如下移动:

  1. 连续地将核心的位置从当前位置移动到任意位置。
  2. 用第三条机械臂抓握住一个岩点(可以与某条其他机械臂抓住相同的岩点)。
  3. 让一条机械臂松开其抓握的岩点。注意,只有当另外两条机械臂已经抓握住不同岩点时,才能进行这项操作。

注:我们假设岩点的大小是无穷小,不会影响机器人的移动,比如机械臂、核心轨迹都可以经过岩点。
下面是一个攀岩过程的例子,假设平面上有四个岩点,坐标分别是 (0,0),(1,0),(0,1),(1,2),则下面展示了一个 r=2​ 时,即机械臂长度至多为 2​ 时的攀岩方案。

steps.jpg

  1. 初始时,可怜将机器人的核心放在 (0.5,0) 处,第三根手臂随意地放在 (1,1) 处。
  2. 机器人的第三机械臂抓握住岩点 p3​。
  3. 机器人的第二机械臂松开,并移动到 (1,0.5) 处。
  4. 机器人的核心移动至 (1,1),此时第一机械臂的长度到达极限 2​。
  5. 机器人的第二机械臂抓握住岩点 p4​。
  6. 机器人的第一机械臂松开,并抓握住岩点 p4​,完成攀岩目标。

显然,机器人的机械臂长度越短,对可怜的路径规划能力要求越高。但是这个机械臂的长度也不能太短,不然机器人可能无论如何都无法完成攀岩任务。比如在上述任务中,如果机械臂长度小于 0.5,那么机器人将无法同时抓握 p1​ 和 p2​,这意味着它连开始攀岩都无法做到,更别说完成攀岩任务了。
因此,为了在合理范围内尽可能地挑战自己的大脑,可怜希望你对于攀岩馆中的每一项攀岩任务,都帮她计算(在可能完成攀岩任务的前提下)机械臂的最短长度是多少。

输入格式:

第一行输入一个整数 t (t≤100),表示攀岩馆内攀岩任务的数量。
每个任务的第一行都是一个整数 n (3≤n≤1500),表示攀岩任务涉及的岩点数量。
接下来 n 行,每行两个整数 (xi​,yi​),表示第 i 个岩点的坐标。输入保证 0≤xi​,yi​≤106 且同一个攀岩任务中的岩点坐标两两不同。
输入保证满足 n>100 的数据不超过 3 组。

输出格式:

对于每个攀岩任务输出一行一个浮点数,表示最短可能的机械臂长度。你的答案在绝对误差或者相对误差不超过 10−6 的情况下都会被视为正确。

输入样例:

4
4
0 0
1 0
0 1
1 2
4
0 0
2 0
0 3
2 2
7
0 0
1 0
2 0
2 1
2 2
1 2
0 2
15
13 2
13 3
12 44
6 17
4 71
14 58
4 49
2 51
8 37
1 18
5 43
5 35
1 84
10 23
13 69

输出样例:

0.99999999498
1.41421355524
1.00000000498
10.13227667717

样例解释

第一组数据和题面中的攀岩任务一致,其最小机械臂长度为 1,而样例输出的答案在 10−6 的误差范围内,故会被视为正确。
下面给出了一个机械臂长度为 1 时的攀岩方案。攀岩开始时,机器人的核心与岩点 p1​ 重叠,第三机械臂在长度为 0 的情况下抓住了这个岩点,且第一机械臂额外抓住了岩点 p3​。接着,第三机械臂松开岩点 p1​,并随着核心移动至点 (1,1)。然后,第三机械臂伸出并抓握住岩点 p4​。最后,第二机械臂松开岩点 p2​ 并同样抓握住岩点 p4​,完成攀岩任务。

屏幕截图 2024-04-19 100157.jpg

第二组数据中,最小机械臂长度为 2​,下图展示了一个对应的攀岩方案。攀岩开始时,机器人的核心位于 (1,1) 且三个机械臂分别抓握住岩点 p1​,p2​ 和 p4​。随后,第二机械臂松开点 p2​ 并抓握住岩点 p4​,完成攀岩任务。注意到此攀岩方案全程没有使用岩点 p3​,这也是被允许的:攀岩任务中不要求使用所有的岩点

屏幕截图 2024-04-19 100514.jpg

代码长度限制

16 KB

时间限制

4000 ms

内存限制

512 MB

栈限制

8192 KB

import java.io.*;
import java.util.*;

public class Main {
    // === 快读 ===
    static final int SZ = 1 << 18;
    static byte[] ibuf = new byte[SZ];
    static int ip = 0, il = 0;
    static InputStream is = System.in;
    static int rb() throws IOException {
        if (ip >= il) { il = is.read(ibuf); ip = 0; }
        return il == -1 ? -1 : ibuf[ip++];
    }
    static int ri() throws IOException {
        int c = rb(), x = 0;
        while (c < '0' || c > '9') c = rb();
        while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = rb(); }
        return x;
    }
    static long rl() throws IOException {
        int c = rb(); long x = 0;
        while (c < '0' || c > '9') c = rb();
        while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = rb(); }
        return x;
    }

    // === Delaunay 三角剖分 (Guibas-Stolfi) ===
    // Quad-edge 用数组: 每条边占 4 个槽 (e, e+1=rot, e+2=rev, e+3=rot of rev)
    // org[e], onext[e], used[e]
    static final int QPOOL = 8_000_000;
    static long[] qox = new long[QPOOL], qoy = new long[QPOOL];
    static int[] qid = new int[QPOOL]; // 原始点 id
    static int[] onext = new int[QPOOL];
    static boolean[] used = new boolean[QPOOL];
    static int qcnt;

    static void qinit() { qcnt = 0; }
    static int rot(int e) { return (e & ~3) | ((e + 1) & 3); }
    static int rev(int e) { return (e & ~3) | ((e + 2) & 3); }
    static int lnext(int e) { return rot(onext[rot(rev(e))]); }
    static int oprev(int e) { return rot(onext[rot(e)]); }
    static long destX(int e) { return qox[rev(e)]; }
    static long destY(int e) { return qoy[rev(e)]; }
    static int destId(int e) { return qid[rev(e)]; }

    static int makeEdge(long ax, long ay, int ai, long bx, long by, int bi) {
        int e = qcnt; qcnt += 4;
        qox[e] = ax; qoy[e] = ay; qid[e] = ai;
        qox[e|2] = bx; qoy[e|2] = by; qid[e|2] = bi;
        qox[e|1] = qoy[e|1] = qox[e|3] = qoy[e|3] = (long) 1e18;
        qid[e|1] = qid[e|3] = -1;
        onext[e] = e; onext[e|2] = e|2;
        onext[e|1] = e|3; onext[e|3] = e|1;
        used[e] = used[e|1] = used[e|2] = used[e|3] = false;
        return e;
    }

    static void splice(int a, int b) {
        int t1 = onext[rot(onext[a])], t2 = onext[rot(onext[b])];
        int t3 = onext[a], t4 = onext[b];
        onext[rot(onext[a])] = t2;
        onext[rot(onext[b])] = t1;
        onext[a] = t4;
        onext[b] = t3;
    }

    static void delEdge(int e) {
        splice(e, oprev(e));
        splice(rev(e), oprev(rev(e)));
    }

    static int connect(int a, int b) {
        int e = makeEdge(destX(a), destY(a), destId(a), qox[b], qoy[b], qid[b]);
        splice(e, lnext(a));
        splice(rev(e), b);
        return e;
    }

    static long cross(long ox, long oy, long ax, long ay, long bx, long by) {
        return (ax - ox) * (by - oy) - (ay - oy) * (bx - ox);
    }

    static boolean leftOf(long px, long py, int e) {
        return cross(qox[e], qoy[e], destX(e), destY(e), px, py) > 0;
    }
    static boolean rightOf(long px, long py, int e) {
        return cross(qox[e], qoy[e], destX(e), destY(e), px, py) < 0;
    }

    // in_circle: 用 double 计算行列式 (坐标 ≤ 10^6, 精度足够)
    static boolean inCircle(long ax, long ay, long bx, long by,
                            long cx, long cy, long dx, long dy) {
        double dax = ax - dx, day = ay - dy, das = dax * dax + day * day;
        double dbx = bx - dx, dby = by - dy, dbs = dbx * dbx + dby * dby;
        double dcx = cx - dx, dcy = cy - dy, dcs = dcx * dcx + dcy * dcy;
        double det = dax * (dby * dcs - dcy * dbs)
                   - dbx * (day * dcs - dcy * das)
                   + dcx * (day * dbs - dby * das);
        return det > 0;
    }

    // Delaunay build (divide & conquer), 返回 {leftEdge, rightEdge}
    static long[] spx, spy; static int[] sid; // 排序后的点
    static int[] buildRes = new int[2]; // 返回值复用

    static void buildTr(int l, int r) {
        if (r - l + 1 == 2) {
            int e = makeEdge(spx[l], spy[l], sid[l], spx[r], spy[r], sid[r]);
            buildRes[0] = e; buildRes[1] = rev(e); return;
        }
        if (r - l + 1 == 3) {
            int a = makeEdge(spx[l], spy[l], sid[l], spx[l+1], spy[l+1], sid[l+1]);
            int b = makeEdge(spx[l+1], spy[l+1], sid[l+1], spx[r], spy[r], sid[r]);
            splice(rev(a), b);
            long sg = cross(spx[l], spy[l], spx[l+1], spy[l+1], spx[r], spy[r]);
            if (sg == 0) { buildRes[0] = a; buildRes[1] = rev(b); return; }
            int c = connect(b, a);
            if (sg > 0) { buildRes[0] = a; buildRes[1] = rev(b); }
            else { buildRes[0] = rev(c); buildRes[1] = c; }
            return;
        }
        int mid = (l + r) / 2;
        buildTr(l, mid);
        int ldo = buildRes[0], ldi = buildRes[1];
        buildTr(mid + 1, r);
        int rdi = buildRes[0], rdo = buildRes[1];
        // 找下切线
        while (true) {
            if (leftOf(qox[rdi], qoy[rdi], ldi)) { ldi = lnext(ldi); continue; }
            if (rightOf(qox[ldi], qoy[ldi], rdi)) { rdi = onext[rev(rdi)]; continue; }
            break;
        }
        int basel = connect(rev(rdi), ldi);
        if (qox[ldi] == qox[ldo] && qoy[ldi] == qoy[ldo]) ldo = rev(basel);
        if (qox[rdi] == qox[rdo] && qoy[rdi] == qoy[rdo]) rdo = basel;
        // 合并
        while (true) {
            int lc = onext[rev(basel)];
            boolean lv = rightOf(destX(lc), destY(lc), basel);
            if (lv) {
                while (inCircle(destX(basel), destY(basel), qox[basel], qoy[basel],
                        destX(lc), destY(lc), destX(onext[lc]), destY(onext[lc]))) {
                    int t = onext[lc]; delEdge(lc); lc = t;
                }
                lv = rightOf(destX(lc), destY(lc), basel);
            }
            int rc = oprev(basel);
            boolean rv = rightOf(destX(rc), destY(rc), basel);
            if (rv) {
                while (inCircle(destX(basel), destY(basel), qox[basel], qoy[basel],
                        destX(rc), destY(rc), destX(oprev(rc)), destY(oprev(rc)))) {
                    int t = oprev(rc); delEdge(rc); rc = t;
                }
                rv = rightOf(destX(rc), destY(rc), basel);
            }
            if (!lv && !rv) break;
            if (!lv || (rv && inCircle(destX(lc), destY(lc), qox[lc], qoy[lc],
                    qox[rc], qoy[rc], destX(rc), destY(rc))))
                basel = connect(rc, rev(basel));
            else
                basel = connect(rev(basel), rev(lc));
        }
        buildRes[0] = ldo; buildRes[1] = rdo;
    }

    // 提取三角形, 返回 int[][3] (原始点 id)
    static int[][] triangulate(long[] px, long[] py, int[] id, int n) {
        if (n < 3) return new int[0][];
        qinit();
        // 排序
        Integer[] ord = new Integer[n];
        for (int i = 0; i < n; i++) ord[i] = i;
        Arrays.sort(ord, (a, b) -> px[a] != px[b] ? Long.compare(px[a], px[b]) : Long.compare(py[a], py[b]));
        spx = new long[n]; spy = new long[n]; sid = new int[n];
        for (int i = 0; i < n; i++) { spx[i] = px[ord[i]]; spy[i] = py[ord[i]]; sid[i] = id[ord[i]]; }
        buildTr(0, n - 1);
        int le = buildRes[0];
        // 遍历三角形
        int e = le;
        ArrayList<Integer> stk = new ArrayList<>();
        stk.add(e);
        while (cross(qox[e], qoy[e], destX(e), destY(e),
                destX(onext[e]), destY(onext[e])) > 0) e = onext[e];
        // BFS 遍历面
        ArrayList<int[]> tris = new ArrayList<>();
        ArrayList<Integer> triIds = new ArrayList<>(); // 三角形顶点 id
        // 用 add 函数遍历
        int stkIdx = 0;
        // 先处理第一个面
        {
            int cur = e;
            do {
                used[cur] = true;
                triIds.add(qid[cur]);
                stk.add(rev(cur));
                cur = lnext(cur);
            } while (cur != e);
        }
        triIds.clear(); // 第一个面可能是外部面
        stkIdx = 0;
        while (stkIdx < stk.size()) {
            e = stk.get(stkIdx++);
            if (used[e]) continue;
            int cur = e;
            int cnt = 0;
            int[] tri = new int[3];
            do {
                used[cur] = true;
                if (cnt < 3) tri[cnt] = qid[cur];
                cnt++;
                stk.add(rev(cur));
                cur = lnext(cur);
            } while (cur != e);
            if (cnt == 3) tris.add(tri);
        }
        return tris.toArray(new int[0][]);
    }

    // === 主算法 ===
    static final int MAXN = 1505;
    static final double INF = 1e18;
    static double[][] d2 = new double[MAXN][MAXN];
    static double[][] best = new double[MAXN][MAXN];
    static int[][] adj; // Delaunay 邻居

    // 索引堆
    static int[] pq, qp; static int pqSz;
    static double[] key;

    static void swim(int k) {
        while (k > 1 && key[pq[k >> 1]] > key[pq[k]]) {
            int t = pq[k]; pq[k] = pq[k >> 1]; pq[k >> 1] = t;
            qp[pq[k]] = k; qp[pq[k >> 1]] = k >> 1;
            k >>= 1;
        }
    }
    static void sink(int k) {
        while ((k << 1) <= pqSz) {
            int j = k << 1;
            if (j < pqSz && key[pq[j]] > key[pq[j + 1]]) j++;
            if (key[pq[k]] <= key[pq[j]]) break;
            int t = pq[k]; pq[k] = pq[j]; pq[j] = t;
            qp[pq[k]] = k; qp[pq[j]] = j;
            k = j;
        }
    }
    static void pqPush(int id, double c) {
        if (c < key[id]) {
            key[id] = c;
            if (qp[id] != -1) swim(qp[id]);
            else { qp[id] = ++pqSz; pq[pqSz] = id; swim(pqSz); }
        }
    }
    static int pqPop() {
        int r = pq[1];
        int t = pq[1]; pq[1] = pq[pqSz]; pq[pqSz] = t;
        qp[pq[1]] = 1; qp[r] = -1;
        pqSz--;
        if (pqSz > 0) sink(1);
        return r;
    }

    static double circumR2(int u, int v, int k) {
        double a2 = d2[u][v], b2 = d2[u][k], c2 = d2[v][k];
        double mx, m1, m2;
        if (a2 >= b2 && a2 >= c2) { mx = a2; m1 = b2; m2 = c2; }
        else if (b2 >= c2) { mx = b2; m1 = a2; m2 = c2; }
        else { mx = c2; m1 = a2; m2 = b2; }
        if (m1 + m2 <= mx) return mx * 0.25;
        double tt = m1 + m2 - mx;
        double den = 4.0 * m1 * m2 - tt * tt;
        return mx * m1 * m2 / den;
    }

    static void solve() throws IOException {
        int n = ri();
        long[] px = new long[n], py = new long[n];
        for (int i = 0; i < n; i++) { px[i] = rl(); py[i] = rl(); }
        if (n < 3) {
            if (n == 2) {
                double dx = px[0] - px[1], dy = py[0] - py[1];
                System.out.printf("%.10f%n", Math.sqrt(dx * dx + dy * dy) * 0.5);
            } else System.out.printf("%.10f%n", 0.0);
            return;
        }
        // 距离矩阵
        for (int i = 0; i < n; i++) {
            d2[i][i] = 0;
            for (int j = i + 1; j < n; j++) {
                long dx = px[i] - px[j], dy = py[i] - py[j];
                d2[i][j] = d2[j][i] = (double)(dx * dx + dy * dy);
            }
        }
        // Delaunay
        int[] ids = new int[n]; for (int i = 0; i < n; i++) ids[i] = i;
        int[][] tris = triangulate(px, py, ids, n);
        // 邻接表
        adj = new int[n][];
        @SuppressWarnings("unchecked")
        ArrayList<Integer>[] tmp = new ArrayList[n];
        for (int i = 0; i < n; i++) tmp[i] = new ArrayList<>();
        for (int[] tr : tris) {
            int a = tr[0], b = tr[1], c = tr[2];
            tmp[a].add(b); tmp[a].add(c);
            tmp[b].add(a); tmp[b].add(c);
            tmp[c].add(a); tmp[c].add(b);
        }
        for (int i = 0; i < n; i++) {
            TreeSet<Integer> s = new TreeSet<>(tmp[i]);
            adj[i] = new int[s.size()]; int j = 0;
            for (int v : s) adj[i][j++] = v;
        }
        // 初始化
        for (int i = 0; i < n; i++) Arrays.fill(best[i], 0, n, INF);
        int lim = n * n;
        key = new double[lim]; Arrays.fill(key, 0, lim, INF);
        qp = new int[lim]; Arrays.fill(qp, 0, lim, -1);
        pq = new int[lim + 1]; pqSz = 0;

        double sc = d2[0][1] * 0.25;
        best[0][1] = best[1][0] = sc;
        pqPush(0 * n + 1, sc);
        pqPush(1 * n + 0, sc);

        double ans = INF;
        while (pqSz > 0) {
            int id = pqPop();
            double r2 = key[id];
            if (r2 >= ans) continue;
            int u = id / n, v = id % n;
            if (r2 > best[u][v]) continue;
            if (u == n - 1 || v == n - 1) { ans = r2; break; }

            for (int k : adj[u]) {
                if (k == u || k == v) continue;
                double cr2 = circumR2(u, v, k);
                double cost = cr2 > r2 ? cr2 : r2;
                if (cost >= ans) continue;
                if (cost < best[u][k]) {
                    best[u][k] = best[k][u] = cost;
                    pqPush(u < k ? u * n + k : k * n + u, cost);
                }
                if (cost < best[v][k]) {
                    best[v][k] = best[k][v] = cost;
                    pqPush(v < k ? v * n + k : k * n + v, cost);
                }
            }
            for (int k : adj[v]) {
                if (k == u || k == v) continue;
                double cr2 = circumR2(u, v, k);
                double cost = cr2 > r2 ? cr2 : r2;
                if (cost >= ans) continue;
                if (cost < best[u][k]) {
                    best[u][k] = best[k][u] = cost;
                    pqPush(u < k ? u * n + k : k * n + u, cost);
                }
                if (cost < best[v][k]) {
                    best[v][k] = best[k][v] = cost;
                    pqPush(v < k ? v * n + k : k * n + v, cost);
                }
            }
        }
        System.out.printf("%.10f%n", Math.sqrt(ans));
    }

    public static void main(String[] args) throws Exception {
        new Thread(null, () -> {
            try {
                int t = ri();
                while (t-- > 0) solve();
            } catch (IOException e) { throw new RuntimeException(e); }
        }, "m", 1 << 26).start();
    }
}

cpp(30/30):

1. 【问题抽象】

本题核心是在点集中寻找一条从起点(0,1)到终点(包含n-1)的路径,使得路径上连续三点 * 构成的外接圆直径(或两点间距)的最大值达到最小。

状态定义:min_dist[u][v] 表示当前到达点对 (u, v) 时的最小代价平方 r^2。 

2. 【几何策略与公式】

对于点 u, v 和新加入的点 k 构成的三角形,设边长平方分别为 a^2, b^2, c^2 (a为最长边):

钝角/直角三角形 (b^2 + c^2 <= a^2):

外接圆直径由最长边决定,代价 cost = a^2 / 4。

锐角三角形 (b^2 + c^2 > a^2):

用外接圆半径公式 R^2 = (a^2 * b^2 * c^2) / (4*a^2*b^2 - (a^2+b^2-c^2)^2)。 

代码中简化为:num = a^2 * b^2 * c^2, den = 4*b^2*c^2 - (b^2+c^2-a^2)^2。

3. 【核心优化技巧】  

剪枝策略:

直径剪枝:若 d(u,k)^2 / 4 >= 当前已知最优解,则无需进行后续复杂的锐角代价计算。 

对于特殊构造的稀疏图或长边,几何计算开销巨大。

方案:三角形 (u, v, k) 的最小覆盖圆半径 R 一定满足 $R \ge \text{dist}(u, k) / 2$。 如果边 $(u, k)$ 本身的代价 $\text{dist}^2(u, k)/4$ 已经超过了目标状态 $(u, k)$ 当前已知的最优解,那么无论 $v$ 在哪里,都无法通过当前操作优化$(u, k)$

// 仅需一次乘法和比较,剪掉大量无效几何计算
if (distMat[u][k] * 0.25 >= min_dist[u][k]) skip_update_u = true;

惰性除法:锐角三角形外接圆半径公式需要浮点除法,指令周期极长。

方案:利用不等式变形,将除法转换为乘法。

$\text{Cost} < \text{Limit} \iff \frac{\text{Num}}{\text{Den}} < \text{Limit} \iff \text{Num} < \text{Limit} \times \text{Den}$

/*
 * 核心优化思路:
 * 1. 内存: 维护全对称 min_dist[N][N],保证读取时均为 Stride-1 连续访问 。
 * 2. 剪枝: 直径剪枝 (d^2/4 >= best),过滤稀疏长边。
 * 3. 运算: 惰性除法 (乘法代替除法判定),Lambda 强制内联,循环拆分消除分支。
 */
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <cstring>
#include <cstdio>

using namespace std;

// === 快读 ===
const int BUF_SIZE = 1 << 18;
char buffer[BUF_SIZE];
int buf_ptr = BUF_SIZE, buf_len = 0;

inline int next_char() {
    if (buf_ptr >= buf_len) {
        buf_ptr = 0;
        buf_len = fread(buffer, 1, BUF_SIZE, stdin);
        if (buf_len == 0) return EOF;
    }
    return buffer[buf_ptr++];
}
inline bool scan_int(int &x) {
    int c = next_char();
    while (c != EOF && (c < '0' || c > '9')) c = next_char();
    if (c == EOF) return false;
    x = 0;
    while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = next_char(); }
    return true;
}
inline void scan_long(long long &x) {
    int c = next_char();
    while (c != EOF && (c < '0' || c > '9')) c = next_char();
    x = 0;
    while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = next_char(); }
}

const int MAXN = 1505;
const int MAX_ID = MAXN * MAXN + 7; 
const double INF = 1e18;

long long px[MAXN], py[MAXN];
double distMat[MAXN][MAXN];
double min_dist[MAXN][MAXN]; // 对称存储

// 手写堆
int pq[MAX_ID], qp[MAX_ID], pqSize = 0;
double keys[MAX_ID];

inline bool greater_idx(int i, int j) { return keys[pq[i]] > keys[pq[j]]; }
inline void exch(int i, int j) {
    int swap = pq[i]; pq[i] = pq[j]; pq[j] = swap;
    qp[pq[i]] = i; qp[pq[j]] = j;
}
void swim(int k) { while (k > 1 && greater_idx(k >> 1, k)) { exch(k, k >> 1); k >>= 1; } }
void sink(int k) {
    while ((k << 1) <= pqSize) {
        int j = k << 1;
        if (j < pqSize && greater_idx(j, j + 1)) j++;
        if (!greater_idx(k, j)) break;
        exch(k, j); k = j;
    }
}
inline void push_or_update(int id, double cost) {
    if (cost < keys[id]) {
        keys[id] = cost;
        if (qp[id] != -1) swim(qp[id]);
        else { qp[id] = ++pqSize; pq[pqSize] = id; swim(pqSize); }
    }
}
inline int del_min() {
    int minId = pq[1]; exch(1, pqSize--); sink(1); qp[minId] = -1;
    return minId;
}

void solve() {
    int n;
    if (!scan_int(n)) return;
    for (int i = 0; i < n; i++) { scan_long(px[i]); scan_long(py[i]); }
    if (n < 2) { printf("%.10f\n", 0.0); return; }

    // 预处理距离 (double)
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            long long dx = px[i] - px[j], dy = py[i] - py[j];
            double d2 = (double)(dx * dx + dy * dy);
            distMat[i][j] = distMat[j][i] = d2;
        }
    }

    // 初始化
    for (int i = 0; i < n; i++) fill(min_dist[i], min_dist[i] + n, INF);
    int limit = n * n;
    fill(keys, keys + limit, INF);
    fill(qp, qp + limit, -1);
    pqSize = 0;

    double start_cost = distMat[0][1] * 0.25;
    min_dist[0][1] = start_cost; // 对称性暂不需要,因为只推一个
    push_or_update(1, start_cost); // ID: 0*n+1

    double ans = INF;

    while (pqSize > 0) {
        int uId = del_min();
        double r2 = keys[uId];
        
        if (r2 >= ans) continue;
        int u = uId / n, v = uId % n;
        if (r2 > min_dist[u][v]) continue; // 惰性删除
        if (u == n - 1 || v == n - 1) { ans = r2; break; }

        // 缓存指针
        double* d_row_u = min_dist[u];
        double* d_row_v = min_dist[v];
        double* g_row_u = distMat[u];
        double* g_row_v = distMat[v];
        double val_d_uv = distMat[u][v];

        // 核心更新逻辑
        auto process = [&](int k, int id_u, int id_v) __attribute__((always_inline)) {
            double best_u = d_row_u[k];
            double best_v = d_row_v[k];
            if (best_u <= r2 && best_v <= r2) return; // 基础剪枝

            double d_uk = g_row_u[k];
            double d_vk = g_row_v[k];
            
            // 直径剪枝
            if (d_uk * 0.25 >= best_u && d_vk * 0.25 >= best_v) return;

            double cost, a2, b2, c2;
            if (val_d_uv >= d_uk && val_d_uv >= d_vk) { a2 = val_d_uv; b2 = d_uk; c2 = d_vk; }
            else if (d_uk >= val_d_uv && d_uk >= d_vk) { a2 = d_uk; b2 = val_d_uv; c2 = d_vk; }
            else { a2 = d_vk; b2 = val_d_uv; c2 = d_uk; }

            if (b2 + c2 <= a2) { // 钝角/直角
                double geo = a2 * 0.25;
                cost = (geo > r2) ? geo : r2;
            } else { // 锐角 (惰性除法)
                double limit = (best_u > best_v) ? best_u : best_v;
                if (r2 > limit) cost = r2;
                else {
                    double term = b2 + c2 - a2;
                    double den = 4.0 * b2 * c2 - term * term;
                    double num = a2 * b2 * c2;
                    if (num >= limit * den) cost = limit;
                    else {
                        cost = num / den;
                        if (cost < r2) cost = r2;
                    }
                }
            }
            
            // 状态更新 (维护对称性)
            // d_uk*0.25 >= best_u 已在上面判断过,此处只需 check cost
            if (cost < best_u && !(d_uk * 0.25 >= best_u)) { 
                d_row_u[k] = cost; min_dist[k][u] = cost; push_or_update(id_u, cost); 
            }
            if (cost < best_v && !(d_vk * 0.25 >= best_v)) { 
                d_row_v[k] = cost; min_dist[k][v] = cost; push_or_update(id_v, cost); 
            }
        };

        // 循环拆分 (无分支)
        for (int k = 0; k < u; k++) process(k, k * n + u, k * n + v);
        for (int k = u + 1; k < v; k++) process(k, u * n + k, k * n + v);
        for (int k = v + 1; k < n; k++) process(k, u * n + k, v * n + k);
    }
    printf("%.10f\n", sqrt(ans));
}

int main() {
    int t;
    if (scan_int(t)) while (t--) solve();
    return 0;
}

2026.02.15 更新优化版算法 通过Delaunay 三角剖分把每个状态的邻居从 O(n) 降到 O(deg≈6),复杂度从 O(n³) 降到 O(n² log n)。

相关知识点:https://oi-wiki.org/geometry/triangulation/ (Delaunay 三角剖分)

/**
 * L3-039 攀岩
 * 
 * 算法: 边状态 Dijkstra (min-max path)
 * 状态 (u,v) 表示两臂抓着 u 和 v
 * 转移 (u,v)->(v,k): 代价 = max(当前, circumR^2(u,v,k))
 * 
 * 关键优化: 对每个状态 (u,v), 只枚举 k ∈ Delaunay邻居(u) ∪ Delaunay邻居(v)
 * 理由: Delaunay 三角剖分最小化外接圆, 最优的第三点一定是 Delaunay 邻居
 *       (或在以uv为直径的圆内, 此时 circumR=dist(u,v)/2 不增加代价)
 * 
 * 额外: 对每个 (u,v), 还需考虑"以uv为直径的圆内的点"作为免费转移
 *        这些点通过 Delaunay 邻居的邻居可以间接覆盖
 * 
 * 复杂度: O(n^2 * deg * log(n^2)), deg ≈ 6
 */
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,popcnt")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cassert>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

// === 快读 ===
const int BUF = 1 << 18;
char buf[BUF]; int bp = BUF, bl = 0;
inline int gc() {
    if (bp >= bl) { bp = 0; bl = fread(buf, 1, BUF, stdin); if (!bl) return EOF; }
    return buf[bp++];
}
inline bool ri(int &x) {
    int c = gc(); while (c != EOF && (c < '0' || c > '9')) c = gc();
    if (c == EOF) return false;
    x = 0; while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = gc(); }
    return true;
}
inline void rl(ll &x) {
    int c = gc(); while (c != EOF && (c < '0' || c > '9')) c = gc();
    x = 0; while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = gc(); }
}

// === Delaunay 三角剖分 (Guibas-Stolfi, O(n log n)) ===
namespace Delaunay {
    struct Pt {
        ll x, y; int id;
        Pt() {}
        Pt(ll x, ll y, int id = -1) : x(x), y(y), id(id) {}
        Pt operator-(const Pt& p) const { return {x - p.x, y - p.y}; }
        ll cross(const Pt& p) const { return x * p.y - y * p.x; }
        ll cross(const Pt& a, const Pt& b) const { return (a - *this).cross(b - *this); }
        ll dot(const Pt& p) const { return x * p.x + y * p.y; }
        ll sqr() const { return dot(*this); }
        bool operator==(const Pt& p) const { return x == p.x && y == p.y; }
    };

    struct QE {
        Pt org; QE *rot, *onext; bool used;
        QE* rev() const { return rot->rot; }
        QE* lnext() const { return rot->rev()->onext->rot; }
        QE* oprev() const { return rot->onext->rot; }
        Pt dest() const { return rev()->org; }
    };

    const int POOL = 2000000;
    QE pool[POOL]; int pcnt;
    void init() { pcnt = 0; }

    QE* make_edge(Pt a, Pt b) {
        QE *e1 = &pool[pcnt++], *e2 = &pool[pcnt++];
        QE *e3 = &pool[pcnt++], *e4 = &pool[pcnt++];
        e1->org = a; e2->org = b;
        e3->org = e4->org = {(ll)1e18, (ll)1e18};
        e1->rot = e3; e2->rot = e4; e3->rot = e2; e4->rot = e1;
        e1->onext = e1; e2->onext = e2; e3->onext = e4; e4->onext = e3;
        e1->used = e2->used = e3->used = e4->used = false;
        return e1;
    }

    void splice(QE* a, QE* b) {
        swap(a->onext->rot->onext, b->onext->rot->onext);
        swap(a->onext, b->onext);
    }

    void del_edge(QE* e) {
        splice(e, e->oprev());
        splice(e->rev(), e->rev()->oprev());
    }

    QE* connect(QE* a, QE* b) {
        QE* e = make_edge(a->dest(), b->org);
        splice(e, a->lnext());
        splice(e->rev(), b);
        return e;
    }

    bool left_of(Pt p, QE* e) { return p.cross(e->org, e->dest()) > 0; }
    bool right_of(Pt p, QE* e) { return p.cross(e->org, e->dest()) < 0; }

    bool in_circle(Pt a, Pt b, Pt c, Pt d) {
        __int128 A = a.sqr(), B = b.sqr(), C = c.sqr(), D = d.sqr();
        __int128 det = 0;
        det += (__int128)(a.x) * ((__int128)(b.y) * C - (__int128)(c.y) * B
             - (__int128)(b.y) * D + (__int128)(d.y) * B
             + (__int128)(c.y) * D - (__int128)(d.y) * C);
        det -= (__int128)(b.x) * ((__int128)(a.y) * C - (__int128)(c.y) * A
             - (__int128)(a.y) * D + (__int128)(d.y) * A
             + (__int128)(c.y) * D - (__int128)(d.y) * C);
        det += (__int128)(c.x) * ((__int128)(a.y) * B - (__int128)(b.y) * A
             - (__int128)(a.y) * D + (__int128)(d.y) * A
             + (__int128)(b.y) * D - (__int128)(d.y) * B);
        det -= (__int128)(d.x) * ((__int128)(a.y) * B - (__int128)(b.y) * A
             - (__int128)(a.y) * C + (__int128)(c.y) * A
             + (__int128)(b.y) * C - (__int128)(c.y) * B);
        return det > 0;
    }

    pair<QE*, QE*> build(int l, int r, vector<Pt>& p) {
        if (r - l + 1 == 2) {
            QE* e = make_edge(p[l], p[r]);
            return {e, e->rev()};
        }
        if (r - l + 1 == 3) {
            QE *a = make_edge(p[l], p[l+1]), *b = make_edge(p[l+1], p[r]);
            splice(a->rev(), b);
            int sg = (p[l].cross(p[l+1], p[r]) > 0) ? 1 : (p[l].cross(p[l+1], p[r]) < 0) ? -1 : 0;
            if (sg == 0) return {a, b->rev()};
            QE* c = connect(b, a);
            return sg == 1 ? make_pair(a, b->rev()) : make_pair(c->rev(), c);
        }
        int mid = (l + r) / 2;
        auto [ldo, ldi] = build(l, mid, p);
        auto [rdi, rdo] = build(mid + 1, r, p);
        while (true) {
            if (left_of(rdi->org, ldi)) { ldi = ldi->lnext(); continue; }
            if (right_of(ldi->org, rdi)) { rdi = rdi->rev()->onext; continue; }
            break;
        }
        QE* basel = connect(rdi->rev(), ldi);
        auto valid = [&](QE* e) { return right_of(e->dest(), basel); };
        if (ldi->org == ldo->org) ldo = basel->rev();
        if (rdi->org == rdo->org) rdo = basel;
        while (true) {
            QE* lc = basel->rev()->onext;
            if (valid(lc))
                while (in_circle(basel->dest(), basel->org, lc->dest(), lc->onext->dest()))
                    { QE* t = lc->onext; del_edge(lc); lc = t; }
            QE* rc = basel->oprev();
            if (valid(rc))
                while (in_circle(basel->dest(), basel->org, rc->dest(), rc->oprev()->dest()))
                    { QE* t = rc->oprev(); del_edge(rc); rc = t; }
            if (!valid(lc) && !valid(rc)) break;
            if (!valid(lc) || (valid(rc) && in_circle(lc->dest(), lc->org, rc->org, rc->dest())))
                basel = connect(rc, basel->rev());
            else
                basel = connect(basel->rev(), lc->rev());
        }
        return {ldo, rdo};
    }

    // 返回三角形列表 (id_a, id_b, id_c)
    vector<array<int,3>> triangulate(vector<Pt>& pts) {
        int n = pts.size();
        if (n < 3) return {};
        init();
        vector<Pt> p = pts;
        sort(p.begin(), p.end(), [](const Pt& a, const Pt& b) {
            return a.x < b.x || (a.x == b.x && a.y < b.y);
        });
        auto [le, re] = build(0, n - 1, p);
        // 遍历所有三角形
        QE* e = le;
        vector<QE*> edges = {e};
        while (e->onext->dest().cross(e->dest(), e->org) < 0) e = e->onext;
        vector<Pt> tri_pts;
        auto add = [&]() {
            QE* cur = e;
            do {
                cur->used = true;
                tri_pts.push_back(cur->org);
                edges.push_back(cur->rev());
                cur = cur->lnext();
            } while (cur != e);
        };
        add();
        tri_pts.clear();
        int idx = 0;
        while (idx < (int)edges.size()) {
            if (!(e = edges[idx++])->used) add();
        }
        vector<array<int,3>> res;
        for (int i = 0; i < (int)tri_pts.size(); i += 3) {
            res.push_back({tri_pts[i].id, tri_pts[i+1].id, tri_pts[i+2].id});
        }
        return res;
    }
}

// === 主算法 ===
const int MAXN = 1505;
const double INF = 1e18;

ll px[MAXN], py[MAXN];
double d2[MAXN][MAXN]; // dist^2 矩阵
double best[MAXN][MAXN]; // best[u][v] = 到达边(u,v)的最小瓶颈 r^2
vector<int> adj[MAXN]; // Delaunay 邻居

// 手写堆 (索引堆)
const int MAXID = MAXN * MAXN;
int pq[MAXID + 1], qp[MAXID + 1], pqSz;
double key[MAXID + 1];

inline bool gt(int i, int j) { return key[pq[i]] > key[pq[j]]; }
inline void ex(int i, int j) { swap(pq[i], pq[j]); qp[pq[i]] = i; qp[pq[j]] = j; }
void swim(int k) { while (k > 1 && gt(k >> 1, k)) { ex(k, k >> 1); k >>= 1; } }
void sink(int k) {
    while ((k << 1) <= pqSz) {
        int j = k << 1;
        if (j < pqSz && gt(j, j + 1)) j++;
        if (!gt(k, j)) break;
        ex(k, j); k = j;
    }
}
inline void pq_push(int id, double c) {
    if (c < key[id]) {
        key[id] = c;
        if (qp[id] != -1) swim(qp[id]);
        else { qp[id] = ++pqSz; pq[pqSz] = id; swim(pqSz); }
    }
}
inline int pq_pop() { int r = pq[1]; ex(1, pqSz--); sink(1); qp[r] = -1; return r; }

// 三点最小覆盖圆半径的平方
inline double circumR2(int u, int v, int k) {
    double a2 = d2[u][v], b2 = d2[u][k], c2 = d2[v][k];
    // 找最长边
    double mx, m1, m2;
    if (a2 >= b2 && a2 >= c2) { mx = a2; m1 = b2; m2 = c2; }
    else if (b2 >= a2 && b2 >= c2) { mx = b2; m1 = a2; m2 = c2; }
    else { mx = c2; m1 = a2; m2 = b2; }
    if (m1 + m2 <= mx) return mx * 0.25; // 钝角/直角
    // 锐角: R^2 = a^2*b^2*c^2 / (4*b^2*c^2 - (b^2+c^2-a^2)^2)
    double t = m1 + m2 - mx;
    double den = 4.0 * m1 * m2 - t * t;
    return mx * m1 * m2 / den;
}

void solve() {
    int n;
    if (!ri(n)) return;
    for (int i = 0; i < n; i++) { rl(px[i]); rl(py[i]); }
    if (n < 3) {
        if (n == 2) {
            double dx = px[0] - px[1], dy = py[0] - py[1];
            printf("%.10f\n", sqrt(dx * dx + dy * dy) * 0.5);
        } else {
            printf("%.10f\n", 0.0);
        }
        return;
    }

    // 预处理距离矩阵
    for (int i = 0; i < n; i++) {
        d2[i][i] = 0;
        for (int j = i + 1; j < n; j++) {
            ll dx = px[i] - px[j], dy = py[i] - py[j];
            d2[i][j] = d2[j][i] = (double)(dx * dx + dy * dy);
        }
    }

    // Delaunay 三角剖分
    vector<Delaunay::Pt> pts(n);
    for (int i = 0; i < n; i++) pts[i] = {px[i], py[i], i};
    auto tris = Delaunay::triangulate(pts);

    for (int i = 0; i < n; i++) adj[i].clear();
    // 从三角形提取邻接表
    for (auto& [a, b, c] : tris) {
        adj[a].push_back(b); adj[a].push_back(c);
        adj[b].push_back(a); adj[b].push_back(c);
        adj[c].push_back(a); adj[c].push_back(b);
    }
    // 去重
    for (int i = 0; i < n; i++) {
        sort(adj[i].begin(), adj[i].end());
        adj[i].erase(unique(adj[i].begin(), adj[i].end()), adj[i].end());
    }

    // 初始化
    for (int i = 0; i < n; i++) fill(best[i], best[i] + n, INF);
    int lim = n * n;
    fill(key, key + lim, INF);
    fill(qp, qp + lim, -1);
    pqSz = 0;

    double sc = d2[0][1] * 0.25;
    best[0][1] = best[1][0] = sc;
    pq_push(0 * n + 1, sc);
    pq_push(1 * n + 0, sc);

    double ans = INF;

    while (pqSz > 0) {
        int id = pq_pop();
        double r2 = key[id];
        if (r2 >= ans) continue;
        int u = id / n, v = id % n;
        if (r2 > best[u][v]) continue;
        if (u == n - 1 || v == n - 1) { ans = r2; break; }

        // 枚举第三个点 k: 只枚举 u 和 v 的 Delaunay 邻居
        // 对于 u 的邻居 k: 转移到 (u,k) 和 (v,k)
        // 对于 v 的邻居 k: 转移到 (u,k) 和 (v,k)
        auto tryK = [&](int k) __attribute__((always_inline)) {
            if (k == u || k == v) return;
            double cr2 = circumR2(u, v, k);
            double cost = cr2 > r2 ? cr2 : r2;
            if (cost >= ans) return;
            // 更新 (u,k)
            if (cost < best[u][k]) {
                best[u][k] = best[k][u] = cost;
                int id1 = u < k ? u * n + k : k * n + u;
                pq_push(id1, cost);
            }
            // 更新 (v,k)
            if (cost < best[v][k]) {
                best[v][k] = best[k][v] = cost;
                int id2 = v < k ? v * n + k : k * n + v;
                pq_push(id2, cost);
            }
        };

        for (int k : adj[u]) tryK(k);
        for (int k : adj[v]) tryK(k);
    }

    printf("%.10f\n", sqrt(ans));
}

int main() {
    int t;
    if (ri(t)) while (t--) solve();
    return 0;
}

顺便 问一下 @Pretty Boy Fox 你盗我代码然后把博文收费是不是多少有点活不起了?

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐