P6628 [省选联考 2020 B 卷] 丁香之路 解题报告


P6628 [省选联考 2020 B 卷] 丁香之路 解题报告

题目核心思想解读

首先,我们来拆解一下这个题目。我们需要做什么?

  1. 起点和终点:从一个固定的起点 s 出发,要到达一个目标终点 t(这个 t 会从 1 变到 n,我们需要对每个 t 都计算一次答案)。
  2. 必经之路:旅途中,必须把 m 条“丁香花”道路全部走过至少一遍。
  3. 图的结构:我们可以在一张 \(n\) 个点的“完全图”上行走,任意两点 \(i, j\) 之间都有一条边,走过去的时间(也就是边权)是 \(|i-j|\)
  4. 目标:找到一条满足上述条件的最短路径,计算总花费时间。

这个“必须经过所有指定边”的要求,是解决本题的关键。这让我们联想到一个经典的图论问题:中国邮递员问题 (Chinese Postman Problem)。它的核心思想,和欧拉路径息息相关。

什么是欧拉路径?

简单来说,欧拉路径就是一条能“一笔画”走完图中所有边的路径。要满足什么条件才能“一笔画”呢?

  • 欧拉回路(起点和终点是同一个点):图中所有点的度数(连接这个点的边的数量)都必须是偶数,并且图是连通的。
  • 欧拉路径(起点和终点是不同的点):图中只有起点和终点的度数是奇数,其他所有点的度数都是偶数,并且图是连通的。

如何将题目转化为欧拉路径问题?

我们的目标是走完 m 条丁香路,并付出最小的额外代价。总花费可以分解为两部分:

总花费 = (必须走的 m 条丁香路的总长度) + (为了满足要求而额外走的路的总长度)

其中,m 条丁香路的总长度是固定的,我们无法改变。我们能优化的,只有“额外走的路”。怎么走才能让额外代价最小呢?答案是:让我们最终走过的所有边的集合,恰好构成一个从 st 的欧拉路径

这样一来,我们就不需要重复走任何一条丁香路,也能把它们都串起来。

所以,我们的任务就变成了:在原图(有 n 个点,边权为 \(|i-j|\))中,添加一些代价最小的边,使得:

  1. 包含所有 m 条丁香路的图,变成一个连通图。
  2. 在这个新图中,起点 s 和终点 t 的度数是奇数,其他所有点的度数都是偶数。

解题步骤详解

我们来一步步解决这个问题。对于每一个终点 t(题目要求我们从 1 到 n 都计算一遍),我们都需要执行以下流程:

第 0 步:计算基础代价

首先,不管怎么走,m 条丁香路是必走的。我们先把它们的总长度算出来,记为 base_cost
base_cost = sum(|u-v|),对于所有给定的 m 条丁香路 (u,v)

// 对应代码
ll sum = 0;
for(int i=1; i<=m; i++) {
    // ...读入 u, v
    sum += abs(u-v);
}

第 1 步:分析度数(奇偶性问题)

欧拉路径对点的度数有严格要求。我们先统计一下只考虑 m 条丁香路时,每个点的度数。然后,因为我们要从 s 走到 t,这相当于也引入了一条从 st 的路径,所以 st 的度数也要加 1。

现在,我们有了一份包含丁香路和 s->t 路径的度数表。表里可能会有很多度数为奇数的点。我们的目标是只保留 st 的奇数度,把其他所有奇数度的点都变成偶数度。怎么做呢?很简单,将奇数度的点两两配对,在它们之间连边。每连一条边,两个端点的度数都加 1,奇数就变成了偶数。

为了让额外代价最小,我们应该怎么配对呢?
有一个贪心结论:将所有奇数度的点按编号从小到大排序,然后依次将第1个和第2个配对,第3个和第4个配对,...,这样总代价最小。
例如,奇数点是 1, 4, 5, 8。最优配对是 (1,4) 和 (5,8),代价是 |1-4|+|5-8|=3+3=6
这个过程在代码中非常巧妙地实现了:

// 对应代码
ll ans = base_cost;
int pre = 0;
for(int j = 1; j <= n; j++) {
    if (du[j] & 1) { // 如果j的度数是奇数
        if (pre) { // 如果前面已经有一个未配对的奇数点pre
            ans += j - pre; // 配对,增加代价
            pre = 0; // 完成配对
        } else {
            pre = j; // 记录j,等待下一个奇数点
        }
    }
}

j-pre 就等于 |j-pre|,因为循环中 j 总是大于 pre。这部分代价我们称为 pairing_cost

第 2 步:保证图连通(连通性问题)

解决了度数问题后,还有一个要求:图必须是连通的。
最初的 m 条丁香路可能构成好几个独立的连通块(比如 (1,2)(4,5) 就是两个块)。在第 1 步中,我们为了配对奇数点,又增加了一些边,这些边也可能会把一些连通块连接起来。

在完成度数配对后,我们检查一下图现在有几个连通块。如果多于一个,我们就必须在这些连通块之间“搭桥”,把它们全连起来。用什么方法搭桥代价最小呢?最小生成树 (MST)

我们可以把每个连通块看作一个“超级点”,然后在这些超级点之间寻找最短的边来构建一棵最小生成树。

但是这里有个陷阱!我们构建的路径是一条线,不能凭空跳跃。如果我们从A连通块通过一座“桥”到了B连通块,走完B里面的路之后,我们还得原路返回A(或者去往C),才能继续我们的旅程。所以,每一座“桥”(MST中的每一条边)都必须被往返走两次

因此,连接所有连通块的最小代价是:2 * (连接这些连通块的MST的总权值)

这个过程在代码中是这样实现的:

  1. 初始连通块:用并查集(DSU)处理 m 条边,得到初始的连通块。
  2. 更新连通性:在第1步配对奇数点时,增加的边 (pre, j) 也连接了对应的连通块。
  3. 建MST
    • 找出所有可能用来“搭桥”的边。一个简单的策略是:考虑所有相邻点 (i, i+1),如果它们属于不同连通块,就是一座潜在的桥。代码中用了更优化的方法,只考虑了有边的点之间的连接。
    • 将这些潜在的桥边按权值排序。
    • 用 Kruskal 算法(基于并查集)找出 MST。
    • 将 MST 的总权值乘以 2,加到最终答案里。
// 对应代码(Kruskal部分)
vector<edg> g; // 存储潜在的桥
// ... 生成g ...
sort(g.begin(), g.end()); // 按权值排序
for (auto edge : g) {
    if (find(edge.u) != find(edge.v)) { // 如果连接了两个不同的块
        fa[find(edge.u)] = find(edge.v);
        ans += edge.w * 2; // 代价是边权的两倍
    }
}

总结

对于每个终点 t,总的最小花费时间 ans 的计算公式如下:

ans(t) = base_cost + pairing_cost(t) + bridging_cost(t)

  • base_costm 条丁香路的总长度,固定不变。
  • pairing_cost(t):为了修正奇数度点(包括 st)而增加的边的总长度。
  • bridging_cost(t):为了连通所有部分,搭桥产生的代价,等于 2 * MST 权值。

我们对 t = 1, 2, ..., n 分别重复上述计算,就能得到所有答案。

这份解题报告希望能帮助你理解这个问题的思路和巧妙的解法。它完美地融合了欧拉路径、贪心和最小生成树等多个经典算法。

posted @ 2025-07-11 19:51  surprise_ying  阅读(32)  评论(0)    收藏  举报