GF和猫咪的玩具

GitHub跳转原题关系图返回列表

绳索等长时两个环最多拉紧的绳索数就是它们之间的最少绳索数,把题意翻译成无向无权图的点对最短路,Floyd 求全源距离后取最大即图的直径。

OJ: roj

题目 ID: 3211

难度:普及+/提高-

标签:图论最短路Floydpython

创建: 2026-10-02 02:10

更新: 2026-10-10 11:30

形式化题目

给定一张无向图 G=(V,E)G=(V,E):金属环是顶点,∣V∣=n|V| = n,编号 1,2,…,n1, 2, \dots, n;绳索是边,每条边长度相同。同一条绳索的两个端点不同,允许重边。

选两个不同的顶点 L,RL, R 分别向两边拉到最远。当 LL 到 RR 存在多个绳索数相等的连接方法时,只有其中一条被算作拉紧,不得合并统计。

记 dist⁡(u,v)\operatorname{dist}(u,v) 为从 uu 到 vv 最少要经过的绳索数(u,vu, v 不连通时不存在)。求

max⁡u≠vu∼vdist⁡(u,v), \max_{\substack{u \neq v \\ u \sim v}} \operatorname{dist}(u, v),

即把每条绳索看成单位长度时图的直径;没有任何连通点对时答案为 00。

正解

思路

先弄清"拉紧"到底数的是哪些绳索。

每条绳索长度都相同,设长度为 ℓ\ell。把 LL 和 RR 往两边拉,能拉开的距离不可能超过 dist⁡(L,R)⋅ℓ\operatorname{dist}(L,R) \cdot \ell——因为 LL 与 RR 之间至少要跨过 dist⁡(L,R)\operatorname{dist}(L,R) 条绳索。当拉开到这个距离时,LL 与 RR 之间的那条最短路径上的绳索全部被拉成直线,恰好 dist⁡(L,R)\operatorname{dist}(L,R) 条绳索被拉紧。

也就是说,一对环的得分就是它们之间的最少绳索数,而不是任何几何摆放里可能绷直的绳索总数。题面注释把这条口径写得很死:“如果像样例那样 1-2-4-3-5-6-1 构成了一个环,我们认为拉 1 和 3 时只能拉紧一边(1-2-4-3 或 3-5-6-1)而不算全部拉紧”,以及"当两个环之间有几个绳索数相等的连接方法时,只算其中一条连接方法拉紧"。换句话说,"只算一条"是题面的规定:即使多条等长路径在某个摆放里都能绷直,也只取其中一条的长度计数。

样例的六元环正好说明这个差别。环上的边是 1 ⁣− ⁣2 ⁣− ⁣4 ⁣− ⁣3 ⁣− ⁣5 ⁣− ⁣6 ⁣− ⁣11\!-\!2\!-\!4\!-\!3\!-\!5\!-\!6\!-\!1,取 L=1L=1、R=3R=3:

flowchart LR
  n1((1)) --- n2((2)) --- n4((4)) --- n3((3)) --- n5((5)) --- n6((6)) --- n1

从 1 到 3 有两条步数相同的走法:1→2→4→31 \to 2 \to 4 \to 3 和 1→6→5→31 \to 6 \to 5 \to 3,都经过 3 条绳索。如果把它们并起来数,这个六元环的 6 条绳索全都会被算上;但题面规定只算一条,所以 L=1,R=3L=1, R=3 的得分是 33,与样例输出一致。这也解释了 d(1,3)=3d(1,3) = 3 是取较短弧长的结果:环上一对点有两条弧,最少绳索数就是较短的那条。

于是问题变成求图的直径。

因为绳索等长(边权全为 1),"最少绳索数"就是无权图里的最短路长度。答案就是所有连通点对的最短路最大值。LL 与 RR 不连通时一条绳索也拉不紧,贡献 00,不参与取最大。

为什么用 Floyd。

要求的是任意两点之间的距离,n⩽100n \leqslant 100,O(n3)O(n^3) 的 Floyd 一次就能得到完整的距离矩阵:

dist[i][j]←min⁡(dist[i][j], dist[i][k]+dist[k][j]). \text{dist}[i][j] \leftarrow \min\bigl(\text{dist}[i][j],\ \text{dist}[i][k] + \text{dist}[k][j]\bigr).

第 kk 轮结束时,dist[i][j] 表示"只允许把编号 <k< k 的顶点当中转点"时 i→ji \to j 的最少边数;kk 走完 0…n−10 \dots n-1 就放开了所有中转点。1003=106100^3 = 10^6 次松弛在毫秒级完成。

距离矩阵与答案(样例,行列都是环编号):

dist\text{dist} 1 2 3 4 5 6
1 0 1 3 2 2 1
2 1 0 2 1 3 2
3 3 2 0 1 1 2
4 2 1 1 0 2 3
5 2 3 1 2 0 1
6 1 2 2 3 1 0

观察两点:矩阵关于主对角线对称(因为绳索是无向的,d(u,v)=d(v,u)d(u,v) = d(v,u),所以最后只需枚举 u<vu < v);最大值 33 出现在 (1,3)(1,3)、(2,5)(2,5)、(4,6)(4,6) 三对上,正是环上相隔半圈的点对,也就是答案。

最后的细节。 输出前只对连通点对取最大:

python
max((dist[u][v] for u, v in combinations(range(n), 2) if dist[u][v] < INF), default=0)

combinations(range(n), 2) 直接给出所有 u<vu < v 的下标对,if dist[u][v] < INF 跳过不连通点对,default=0 兜住"整张图一条绳索都没有"的情形。

实现上把两件事分开:all_pairs_shortest_paths 只回答"任意两个环之间最少要经过几条绳索",solve 负责读入、调用、取最大并输出。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-10 11:30
 * update_at: 2026-10-10 11:30
 */
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int INF = 1000000000;

int n, m;
int dist[105][105];   // 两环之间最少经过的绳索数

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            dist[i][j] = (i == j ? 0 : INF);

    // 成对读边,奇数落单的 token 自动丢弃(与原版一致)
    int a, b;
    while (cin >> a >> b) {
        dist[a - 1][b - 1] = dist[b - 1][a - 1] = 1;
    }

    // Floyd
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            if (dist[i][k] < INF) {
                for (int j = 0; j < n; j++) {
                    if (dist[i][k] + dist[k][j] < dist[i][j])
                        dist[i][j] = dist[i][k] + dist[k][j];
                }
            }
        }
    }

    int ans = 0;
    for (int u = 0; u < n; u++)
        for (int v = u + 1; v < n; v++)
            if (dist[u][v] < INF && dist[u][v] > ans)
                ans = dist[u][v];
    cout << ans << "\n";
    return 0;
}
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 02:10
# update_at: 2026-10-02 02:10

import sys
from itertools import combinations

INF = 10**9  # 不连通哨兵;n <= 100,真实最短路长度最多 99


def all_pairs_shortest_paths(n: int, edges: list[tuple[int, int]]) -> list[list[int]]:
    """回答"任意两个环之间最少要经过几条绳索":dist[u][v] 即两环距离,INF 表示不连通。"""
    # 环用 0 基编号;对角线为 0,其余先当作不连通
    dist = [[0 if i == j else INF for j in range(n)] for i in range(n)]
    for u, v in edges:
        dist[u][v] = dist[v][u] = 1  # 同一对环之间的重边只留一条,多出的绳永远拉不紧

    # Floyd:第 k 轮允许中转环 k,把整行松弛为"原值"与"先到 k 再走 k->j"的逐列较小者
    for k in range(n):
        row_k = dist[k]
        for i, row_i in enumerate(dist):
            reach = row_i[k]  # i 到中转环 k 的距离
            if reach < INF:
                dist[i] = [min(have, reach + via) for have, via in zip(row_i, row_k)]
    return dist


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    m = next(data)  # 题面声明的绳索数(真实数据可能虚高,按实际 token 成对取边)
    # zip 每轮消费两个 token,奇数落单自动丢弃:与原版按实际数据成对取边一致
    edges = [(a - 1, b - 1) for a, b in zip(data, data)]  # 题面编号 1..n

    dist = all_pairs_shortest_paths(n, edges)

    # 拉紧 L 和 R 时,只有那条最短路径上的绳索会绷直,所以一条路径的长度就是能拉紧的绳索数;
    # 枚举所有环对取最大,不连通的环对一条也拉不紧。
    print(
        max(
            (dist[u][v] for u, v in combinations(range(n), 2) if dist[u][v] < INF),
            default=0,
        )
    )


if __name__ == "__main__":
    solve()

复杂度

  • 时间:建图 O(n2+m)O(n^2 + m),Floyd O(n3)O(n^3),取最大 O(n2)O(n^2),总计 O(n3)O(n^3)。n=100n = 100 时约 10610^6 次松弛,实测单点约 0.020.02 s。
  • 空间:距离矩阵 O(n2)O(n^2),绳索列表 O(m)=O(n2)O(m) = O(n^2),总计 O(n2)O(n^2)。

总结

本题没有几何计算,全部难度集中在把题意翻译成图论语言:

  1. 绳索等长 ⇒ 两个环能拉开的距离正比于"最少要经过几条绳索",被拉紧的绳索恰好构成一条最短路径,得分 = dist⁡(L,R)\operatorname{dist}(L, R);
  2. 题面规定等长的多条连接方法只算一条,所以答案不是这些路径的并集大小(样例的六元环若取并集会得到 6 而不是 3);
  3. 于是答案就是所有连通点对最短路的最大值,即单位边权图的直径,O(n3)O(n^3) 的 Floyd 一次算完所有点对。

要点提醒:重边只需在邻接矩阵里写一次 1(多余的绳索永远拉不紧);不连通的点对不能参与取最大;n⩽100n \leqslant 100 使 O(n3)O(n^3) 富余,若 nn 放大到 10410^4 就该改用 nn 次 BFS,而不是继续用 Floyd。同样的算法在 C++ 里就是 dist[a][b] = dist[b][a] = 1; 加三重 for 松弛,Python 版的差别只是把最内层循环写成 zip 加列表推导。