P12375 「LAOI-12」MST? 解题报告
P12375 「LAOI-12」MST? 解题报告
题目目标
大家好!今天我们来分析一道有趣的构造题。题目要求我们构造一个 \(n\) 个点、\(m\) 条边的连通图,边的权值分别是 \(1, 2, \dots, m\)。我们的目标是,让这张图的最小生成树(MST)的边权之和最大。
这听起来有点矛盾,最小生成树不就是为了求最小的边权和吗?怎么又要让它最大呢?关键就在于“构造”二字。我们可以设计图的连接方式,巧妙地“逼迫”最小生成树算法去选择那些权值很大的边。
核心思想:如何“抬高”最小生成树的成本?
我们先回顾一下Kruskal算法是如何构造最小生成树的:
- 将所有边按权值从小到大排序。
- 依次遍历每条边,如果这条边的两个端点尚未连通,就选择这条边,并合并这两个端点所在的集合。
- 重复直到选够 \(n-1\) 条边。
Kruskal算法是个“小气鬼”,总是优先选择权值最小的边。要想让最终的MST权值和变大,我们必须让它没得选,不得不去选那些大权值的边。
怎么做到呢?答案是:浪费掉那些小权值的边。
一条边在什么情况下会被Kruskal算法“无视”?当这条边的两个端点已经连通时!加上这条边就会形成环,所以Kruskal会跳过它。
所以我们的核心策略就是:按权值从小到大的顺序(\(1, 2, 3, \dots\))考虑每一条边,尽可能地用它来连接两个已经连通的点,从而“浪费”掉它。只有在万不得已(即不加这条边就无法连接新节点)的情况下,才让它成为MST的一部分。
构造策略与规律发现
让我们来模拟一下这个过程,看看哪些边最终会被选入MST。
我们用一个点集合(初始只有一个点)来表示当前已经连通的部分,我们称之为“圈”。
-
边 1 (权值为1):
- 当前只有一个点,无法浪费。必须用它来连接一个新点。
- 选择边 1,连接点1和点2。现在我们的“圈”里有 {1, 2} 两个点了。
- MST边权和:1。
-
边 2 (权值为2):
- “圈”里只有两个点,它们之间已经有边了。我们想连接一个新点3,必须用上这条边。
- 选择边 2,比如连接点2和点3。现在“圈”里有 {1, 2, 3} 三个点(它们互相连通)。
- MST边权和:1 + 2 = 3。
-
边 3 (权值为3):
- 我们的“圈”里有 {1, 2, 3}。点1和点3已经通过点2连通了。太好了!我们可以浪费边3了。
- 浪费边 3,用它连接点1和点3。这样,{1, 2, 3} 就成了一个三点完全图(一个三角形)。这条边不会被Kruskal选入MST。
- MST边权和:仍然是 3。
-
边 4 (权值为4):
- 现在“圈”里的点 {1, 2, 3} 已经紧密连接,无法在内部浪费边了。我们需要连接一个新点4。
- 选择边 4,比如连接点3和点4。“圈”扩大为 {1, 2, 3, 4}。
- MST边权和:3 + 4 = 7。
-
边 5 (权值为5) 和 边 6 (权值为6):
- 我们可以用这两条边来继续“加固”我们的“圈”,把 {1, 2, 3, 4} 变成一个四点完全图。比如用边5连接(1,4),用边6连接(2,4)。
- 浪费边 5 和边 6。
- MST边权和:仍然是 7。
我们发现了一个规律!
被选入MST的边的权值分别是:1, 2, 4, 7, 11, 16, ...
被浪费的边,其权值正好填补了上面数列的空隙。
这个被选中的数列 \(a_i\) 有什么特点?
\(a_2 - a_1 = 1\)
\(a_3 - a_2 = 2\)
\(a_4 - a_3 = 3\)
...
\(a_k - a_{k-1} = k-1\)
这是一个二阶等差数列。通过简单的数学推导(或者直接观察),我们可以得到它的通项公式:
这个公式的含义是:当我们准备引入第 \(k+1\) 个点时(即选择第 \(k\) 条MST边),我们已经把前 \(k\) 个点构成了一个完全图,这消耗了 \(\frac{k(k-1)}{2}\) 条边。所以第 \(k\) 条被选中的MST边,是所有边里的第 \(\frac{k(k-1)}{2} + 1\) 条。
临界点:什么时候不能再“浪费”了?
上面的策略是建立在“我们有足够多的边可以浪费”的前提下的。但如果总边数 \(m\) 有限,我们可能浪费到一半就没边可用了。
设我们已经按照上述策略,选了 \(x\) 条边进入MST,它们的权值分别是 \(a_1, a_2, \dots, a_x\)。此时:
- 我们一共考虑了 \(a_x\) 条边(权值为 \(1 \sim a_x\))。
- 我们还剩下 \(m - a_x\) 条边。
- 我们还需要 \(n-1-x\) 条边才能构成一棵完整的生成树。
如果剩下的边数 \((m - a_x)\) 恰好等于还需要的边数 \((n-1-x)\),那我们就没有选择的余地了,剩下的所有边都必须被选入MST。
所以,我们需要找到一个临界点 \(x\),表示我们最多可以执行 \(x\) 次“浪费-选择”的策略。这个 \(x\) 满足:
把 \(a_x = \frac{x(x-1)}{2} + 1\) 代入并化简,可以得到一个关于 \(x\) 的一元二次不等式:
我们可以用求根公式解出这个不等式的根,取正根并向下取整,就得到了最大的符合条件的整数 \(x\)。
最终答案计算
现在,我们的总答案被清晰地分成了两部分:
-
策略内部分:前 \(x\) 条被我们精心挑选出来的MST边。它们的权值和是 \(\sum_{i=1}^x a_i\)。
这个和也有一个漂亮的求和公式:\[sum_1 = \sum_{i=1}^x \left(\frac{i(i-1)}{2} + 1\right) = \frac{(x^2+5)x}{6} \] -
策略外部分:当策略进行到第 \(x\) 步后,我们没法再浪费边了。此时我们还需要 \(n-1-x\) 条边。为了让MST权值和最大,我们肯定会贪心地选择剩下边中权值最大的那些。
剩下的边是从 \(a_x+1\) 到 \(m\)。其中权值最大的 \(n-1-x\) 条边就是:\(m, m-1, m-2, \dots, m-(n-1-x)+1\)。
这是一个等差数列,它的和为:\[sum_2 = \frac{\text{项数} \times (\text{首项} + \text{末项})}{2} = \frac{(n-1-x) \times (m + m - (n-1-x) + 1)}{2} = \frac{(n-1-x)(2m - n + x + 2)}{2} \]
最终的答案就是这两部分之和,再对 \(998244353\) 取模:
代码实现注意事项
- 解一元二次方程:可以直接使用
sqrt
函数求解,注意浮点数精度。 - 大数运算:由于 \(n, m\) 可达 \(10^{18}\),在计算 \(sum_1\) 和 \(sum_2\) 时,中间过程(如 \(x \cdot x \cdot x\))会超出
long long
的范围。- 最佳方案:使用
__int128
类型,它能轻松容纳中间结果,代码写起来最简洁。 - 备用方案:如果使用
long long
,在做除法(如除以6或2)时要特别小心。因为我们是先算出整数结果再取模,而不是在模意义下做除法。可以写一个辅助函数,判断分子中的哪个乘数能被分母的因子(如2或3)整除,先做除法再做乘法,避免溢出。
- 最佳方案:使用
总结
解这道题的完整流程如下:
- 理解题意:要通过构造图来最大化MST的权值和。
- 确立策略:贪心地“浪费”小编号的边去构成完全图(“圈”),迫使MST选择大编号的边。
- 发现规律:被选入MST的边权 \(a_k\) 构成一个二阶等差数列。
- 寻找临界:当剩余边数不足以再浪费时,策略中止。通过解一元二次不等式找到这个临界点 \(x\)。
- 分段计算:答案分为两部分——按策略选择的前 \(x\) 条边的和,以及贪心选择的最大的后 \(n-1-x\) 条边的和。
- 套用公式:利用推导出的求和公式计算两部分和,相加后取模。
- 注意实现:处理好大数运算溢出的问题。
希望这份报告能帮助你理解这道题的精妙之处!