ABC389G 题解
ABC389G 题解
非常好的题目。一开始做这道题的时候毫无头绪,看了题解之后才懂,感觉也并没有想象中那么难。从“不懂”到“懂了”之间的思维过程是需要仔细体会的。
处理无权图最短路问题的经典方式是分层图:按照节点到 \(1\) 号点的距离分层,距离相同的点在同一层。为了方便表述,下文中成节点到 \(1\) 号点的距离为“深度”。分层给图带来了一些性质:第 \(i\) 层(\(i \neq 0\))中的每个点都必须和 \((i - 1)\) 层中的至少一个点相邻,且不与 \((i - 1)\) 层之前的点相邻;同一层之间的点可以任意连边。我们的 dp 也基于分层图的思想。
首先想想怎么设计状态。我们的大致方向是设 \(f(\dots)\) 表示符合某种条件的图的数量,然后想想状态中需要包含什么来刻画图的性质。容易想到状态需要包含点数和边数。由于我们要统计深度为奇数和深度为偶数的点数相同的图的数量,所以状态中还要包含深度为偶数的点数(一种等价的方法是记录深度为偶数和奇数的点数之差)。
到这里也还没有结束。不妨想想我们需要怎么转移,从中反推状态设计。可以想到一种比较好的转移方式是每次加入新的一层点。如果这样转移,就还需要在状态中记录最后一层(即深度最大层)的信息。这里我们要记录的是最后一层的点数,以及其深度的奇偶性。
综上所述,状态设计为:设 \(f(i, j, k, l, t)\) 为满足以下条件的有标号无向简单图的数量:
- 有 \(i\) 个点和 \(j\) 条边
- 有 \(k\) 个深度为偶数的点
- 最后一层有 \(l\) 个点,其深度模 \(2\) 同余 \(t\)。(\(t \in \{0, 1\}\))
转移时,考虑一次加入新的一层点,以及与这一层点相邻的边。(你会发现其它方式都不方便或者无法转移)假设一次加入 \(x\) 个点和 \(y\) 条边,并且 \(t = 0\),那么转移方程为:
其中,\(g(a, b, c)\) 表示有两层点(区分为“内层”和“外层”),内层有 \(a\) 个点,外层有 \(b\) 个点,内外层之间和外层内部共有 \(c\) 条边时(注意,没有计算内层内部的边数),合法连边方式的数量。
这个转移方程不难理解:从剩下的 \((n - i)\) 个点中选 \(x\) 个点作为新的一层,有 \(\dbinom{n - i}{x}\) 种方式;新加入 \(y\) 条边,有 \(g(l, x, y)\) 种方式;加入 \(x\) 个点和 \(y\) 条边后,新图的点数和边数分别为 \(i + x\) 和 \(j + y\),由于新的一层的点数深度为奇数,所以深度为偶数的点数仍为 \(k\),因此转移到 \(f(i + x, j + y, k, x, 1)\) 这个状态。
对于 \(t = 1\) 的情况,转移方程是类似的。
初始化和答案统计是容易的,详见代码。
然后想想怎么计算 \(g(a, b, c)\)。一开始我尝试找到某种封闭形式直接计算,但这似乎是不可行的,所以还是使用 dp(递推)。
考虑一次在外层加入 \(1\) 个点以及 \(i\) 条与其相连的边。我们需要满足加完之后连边方式是合法的,也就是说:至少有一条边连向内层节点。如果没有这个限制,就有 \(\dbinom{a + b}{i}\) 种连边方式。但这个限制不太好满足,正难则反,考虑容斥:如果所有的边都没有连向内层节点,就说明它们都连向了外层节点,方案数为 \(\dbinom{b}{i}\)。减去这些方案,则合法的方案数为 \(\dbinom{a + b}{i} - \dbinom{b}{i}\)。那么 \(g(a, b, c)\) 的转移方程为:
初始化 \(g(a, 0, 0) = 1\)。
预处理 \(g(\cdot, \cdot, \cdot)\) 后就可以直接 dp 了。
时间复杂度分析:dp 状态数为 \(O(n) \times O(n^2) \times O(n) \times O(n) \times O(1) = O(n^5)\),转移的时间复杂度为 \(O(n) \times O(n^2) = O(n^3)\)。预处理的时间复杂度不是瓶颈,因此总时间复杂度为 \(O(n^8)\)。
这个时间复杂度看似不能通过,但如果循环的上下界卡得比较紧,由于常数较小,是可以 AC 的。我写这道题时由于某个地方上界并未卡紧,TLE 了很久才通过。下面给出我 AC 时各个循环的上下界(伪代码):
for(i: 1 -> n - 1):
for(j: i - 1 -> binom(i, 2)): // binom 表示组合数,下同。
for(k: 0 -> i):
for(l: 1 -> i):
for(t: 0 -> 1):
if(f[i][j][k][l][t] = 0): // 特判 0
continue;
for(x: 1 -> n - i):
for(y: x -> binom(x, 2) + x * l): // 这个上界很重要!
// dp
其中 \(y\) 的上界是 \(\dbinom{x}{2} + x \cdot l\),原因是:新加入的边数最多时,新的一层中任意两个点都有边,数量为 \(\dbinom{x}{2}\),除此之外新的一层中每个点与原先最后一层中的每个点两两连边,数量为 \(x \cdot l\),总数为 \(\dbinom{x}{2} + x \cdot l\)。我写这道题时,由于这个上界没有卡紧(一开始写成了 \(\dbinom{x + i}{2}\),现在看来完全就是错的),TLE 了非常多次,后来对着 这篇题解 改了很久才发现问题。(但我输出转移次数后发现,即使我和他的代码转移次数相同,我花费的时间仍然是他的 \(4 \sim 5\) 倍,原因不明。)