【学习笔记】广义串并联图方法
还是比较【小粉兔】的。
广义串并联图是指一类不存在同胚于 \(K_4\) 的子图的图,翻译成人话就是不存在四个点 \(a, b, c, d\) 使得这四个点之间存在六条除顶点外不相交的路径连接每一对点。
广义串并联图有几个性质:
- \(m \le 2n\),为平面图;
- 通过若干次删 \(1\) 度点,缩 \(2\) 度点,叠合重边,最后一定会仅剩下一个点。
广义串并联图还有其它的一些性质,这里并不加以研究,相对来说最重要的性质为后一个性质。由于在很多问题中,将图删去一度点、缩二度点、叠合重边后的答案都是容易维护的,这使得广义串并联图相关的问题有了很简单的解决方法。
但是实际问题中给出的是广义串并联图的情况并不多,但是其使用的删 \(1\) 度点,缩 \(2\) 度点,叠合重边是可以应用在很多题目中的,我们将这种方法称为广义串并联图方法。
具体来讲,存在一类问题,满足有 \(m \le n + k\) 的限制,其中 \(k\) 是一个较小的常数但 \(n, m\) 均很大,那么我们可以运用上述方法将图缩成一个较小的图,再运用一些复杂度较高的算法解决问题。注意到上述三个操作不会使得 \(m - n\) 增加,也就是说进行任意次上述操作后 \(m - n \le k\) 一定仍然满足,若不存在一二度点时,有所有点的度数 \(\ge 3\),此时有 \(2m \ge 3n\),而又因为 \(m \le n + k\),所以可以得到 \(n \le 2k, m \le 3k\),也就是说使用上述做法就可以将图缩为 \(O(k)\) 级别。
实现上,我们可以拿 map
维护所有的边,拿队列维护所有度数 \(\le 2\) 的点,这样我们就可以快速的删点删边加边了。
具体应用拿例题来看:
不知道哪来的据说是经典题的题
求给 \(n\) 个点 \(m\) 条边的无向图定向后为 DAG 的方案数。
\(n, m \le 10^5, m - n \le 10\)。
首先当 \(n \le 20\) 的时候是经典的问题,这里已经写过,总之就是枚举一个入度为 \(0\) 的子集 \(T\),再容斥掉处子集外还有入度为 \(0\) 的情况,可以推导处容斥系数为 \((-1)^{|T| + 1}\),具体推导把链接的博客里的二项式反演改成子集反演就行了。那么设 \(g_S\) 为 \(S\) 集合之间是否没有连边,那么就有:
这个可以子集卷积 \(O(n^2 2^n)\) 解决。
然后考虑如何将一个图缩为上述限制的图。我们对于每一条边,设两个值 \((f, g)\) 表示 \(u \to v\) 或 \(v \to u\) 连通的方案数(没有定方向)与 \(u, v\) 不连通的方案数,那么考虑三种操作带来的影响:
- 删一度点:那么我们可以直接将 \((2f+g)\) 统计到总答案上,并把这个点删去;
- 缩二度点:
- \(f' = f_1 f_2\),两条边必须同向;
- \(g' = g_1 g_2 + 2 g_1 f_2 + 2g_2 f_1 + 2 f_1 f_2\),要不然都不连通,要不然两个有一个不连通,要不然两个都连通但是方向相反;
- 叠合重边:
- \(f' = f_1 g_2 + f_2 g_1 + f_1 f_2\),要不然其中一条是不连通的,靠另一条边连通,要不然两条边同向;
- \(g' = g_1 g_2\),只有两条边都不连通才可能不连通。
那么最后计算答案的时候,每条边有三种情况,定一个向或断开。为了方便计算,我们先将 \(\prod (f_i + g_i)\) 统计到答案上,因为最后大部分边要不然是断开要不然是定向,而对于上述每次删入度为 \(0\) 的过程时,集合内的所有边都是必须要断掉的,于是对于这些集合内的边需要将答案乘上 \(\prod \frac{g_i}{f_i + g_i}\)。容斥时只需要将集合的权值改成这个就行了。
SNOI2020 生成树
求一个仙人掌图上加一条边的图的生成树个数。
可以发现,这张图是广义串并联图,那么也就是说最后可以缩成一个点。
那么仍然考虑上述做法,设 \((f, g)\) 表示连通与不连通的方案数,那么:
- 删一度点:那么这条边一定是要选的,把 \(f\) 乘到答案上;
- 缩二度点:
- \(f' = f_1 f_2\);
- \(g' = g_1 f_2 + g_2 f_1\),没有 \(g_1 g_2\) 的原因是中间的点必须得和左右两个点其中一个连通。
- 叠合重边:
- \(f' = f_1 g_2 + f_2 g_1\);
- \(g' = g_1 g_2\)。
直接做即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500005, P = 998244353;
int n, m;
map<int, pair<int, int>> e[MAXN];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
e[u][v] = e[v][u] = { e[u][v].first + 1, 1 };
}
queue<int> q;
for (int i = 1; i <= n; i++) if (e[i].size() <= 2) q.push(i);
int ans = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
if (e[u].size() == 0) continue;
for (auto p : e[u]) e[p.first].erase(u);
if (e[u].size() == 1) {
auto [f, g] = e[u].begin()->second;
ans = 1ll * ans * f % P;
} else {
auto [f1, g1] = e[u].begin()->second;
auto [f2, g2] = next(e[u].begin())->second;
int f = 1ll * f1 * f2 % P, g = (1ll * f1 * g2 + 1ll * f2 * g1) % P;
int x = e[u].begin()->first, y = next(e[u].begin())->first;
if (e[x].count(y)) {
auto [f3, g3] = e[x][y];
e[x][y] = e[y][x] = {
(1ll * f * g3 + 1ll * g * f3) % P,
1ll * g * g3 % P
};
} else e[x][y] = e[y][x] = { f, g };
}
for (auto p : e[u]) if (e[p.first].size() <= 2) q.push(p.first);
e[u].clear();
}
printf("%d\n", ans);
return 0;
}
P8426 [JOI Open 2022] 放学路(School Road)
给定一张图,求 \(1 \to n\) 是否存在一条长度不等于 \(1\) 到 \(n\) 的最短路的简单路径。
这个从部分分一步一步分析。
\(m \le 40\)
发现 \(m\) 很小,所以可以考虑爆搜出所有可能的简单路径。经过一些分析可以发现这样的简单路径最多 \(2 \times 10^6\) 种,所以直接搜即可。
\(n \le 18\)
首先我们肯定只需要找出 \(1 \to n\) 的最长路是不是等于最短路,那么直接状压 DP 即可,复杂度 \(O(n^2 2^n)\)。
\(m - n \le 13\)
这就是本文所说到的方法了,考虑直接删一度点缩二度点叠合重边,那么最后的 \(m \le 42\),再用上述的做法做即可。注意 \(1, n\) 不能被删或被缩。
具体来讲,删一度点直接删就行,叠合重边的时候如果两条边的长度不相等,那么就将这条边的边权设为 \(-1\),只要存在一条经过 \(-1\) 的路径那么就一定存在长度不等于最短路的路径了。缩二度点就是如果有一个为 \(-1\) 那么它也是 \(-1\),否则为两条路径长度的和。
图为点双连通分量
注意到如果上述算法最后仅剩下一条边且不为 \(-1\),那么答案就一定为不存在,那么我们大胆猜测,如果最后不只剩下两个点一条边答案就为存在。
虽然这个结论并不在一般图上成立,但是可以证明它在点双上是成立的。或者由于它给出了点双的部分分,你猜测它在点双上是成立的。
考虑如果最后剩下若干个点,那么这些点除了 \(1, n\) 外,所有点的度数均 \(\ge 3\)。
我们首先给这张图定向,方向为最短路的方向,即 \(\mathrm{dis}(1, u) + w + \mathrm{dis}(v, n) = \mathrm{dis}(1, n)\) 的方向。如果不能这么定向那么答案肯定就是存在了。
如果一个点入度小于出度,我们称其为红点,否则称其为蓝点。
考虑 \(1 \to n\) 中经过边数最多的一条路径,那么对于这条路径上的第一个点,其入度一定为 \(1\),否则一定可以从 \(1\) 经过若干个点再到这个点上,所经过的边数会更多。那么,这个点一定是一个红点。同理,路径上最后一个点一定是一个蓝点。
那么在这条路径上,一定存在一条边 \(u \to v\) 满足 \(u\) 为红点且 \(v\) 为蓝点。那么一定存在 \(u \to x\) 与 \(v \to y\) 两条边,那么考虑 \(1 \to y \to v \to u \to x \to n\) 这样一条路径,可以发现这样一条路径一定是简单路径,否则说明 \(1 \to y\) 与 \(x \to n\) 的路径上有重合点 \(z\),这样 \(1 \to x \to z \to y \to n\) 这条路径经过的边数更长,与假设矛盾。而这条路径的长度显然是比最短路要长的,于是答案一定为存在。
那么我们只需要跑上述算法就能很轻松的判定答案了。
正解
实际上正解就很简单了,\(1 \to n\) 的路径上经过了若干个点双,那么只需要分别判定这几个点双之间是否存在大于最短路的路径即可。为了便于实现,可以加一条 \((1, n, \mathrm{dis}(1, n))\) 的边,这样 \(1, n\) 就一定在同一个点双内了,这样就可以直接跑上述算法了。
「2019 集训队互测 Day 3」公园
对每条边维护 \(w_{0/1, 0/1}\) 表示边的两边选某个颜色的最大权值,然后做法就一样了。
带修需要把这个过程记录下来然后用矩阵表示,跑动态 DP。
我不想碰,告辞。