图论学习笔记 4 - 仙人掌图

先扔张图:


为了提前了解我们采用的方法,请先阅读《图论学习笔记 3》。

仙人掌图的定义:一个连通图,且每条边只出现在至多一个环中。

这个图就是仙人掌图。

这个图也是仙人掌图。

而这个图就不是仙人掌图了。

很容易发现,仙人掌图就是在树上连了若干条边( ≥ 1 \ge 1 ≥1 条)。所以可以视为仙人掌图是基环树的扩展。


众所周知,我们通过想象基环树的深搜树形态解决了基环树的一些问题,所以也考虑想象仙人掌图的深搜树。

这里就直接给图了:

很容易发现以下性质:

  • 仙人掌图中,每一条回边互不相交且与环一一对应,环由回边与祖先到子孙的链构成。(这个比较显然,可以简单理解)

  • 任何一个环的 u p up up 点或者是 d n dn dn 点,其子树一定包含的是完整的环。

很容易感性理解。 u p up up 的子树一定包含所有的环点, d n dn dn 的子树一定不包含所有的环点。所以就可以证了。

  • 在每一个点的子树中,至多有一个没有遍历到其对应的 u p up up 点的 d n dn dn 点。

考虑反证法,设我们有一个点 x x x,其子树里面有两个没有遍历到其对应的 u p up up 点的 d n dn dn 点。设两个 u p up up 点为 u p 1 , u p 2 up1,up2 up1,up2,设两个 d n dn dn 点为 d n 1 , d n 2 dn1,dn2 dn1,dn2。

很容易发现,两个 u p up up 点一定是 x x x 的祖先。不妨这里设 u p 1 up1 up1 是 u p 2 up2 up2 的祖先。

容易发现, u p 1 up1 up1 到 x x x 的一整条路径都出现在了两个环中,所以这样是矛盾的,原结论得证。


T425915 仙人掌图最大独立集

首先默认已经做过基环树版本的 骑士 那道题了。

回顾一下那道题的做法,可以发现对于一条回边 u p → d n up \to dn up→dn 构成的环,我们是在原有的 没有上司的舞会那道题 d p dp dp 状态上进行了一个升维,在记录子树根结点有没有选的同时还记录了 d n dn dn 有没有选。

考虑将这种方法扩展到仙人掌图上面,但是我们发现一个结点的子树里面可能有很多的 d n dn dn(并不是只有一个环了),而且数量是会变化的,而我们又不可能 2 n 2^n 2n 记录所有的 d n dn dn 选没选,所以在状态设计方面遇到了一点"困难"。

但是我们发现,上述结论 3 就是为我们量身定制的,因为其他已经被考虑过的环已经不用再考虑(这是仙人掌图,不会出现环套环的情况),所以只需要把目光放在这个没有走到 u p up up 的 d n dn dn 点就行了。

所以就可以设计状态了。至此思路已经成型,直接把那道题的代码拿过来改改就行了。

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 50010;
int n;
vector<int> v[N];
int dp[N][4];
bool stk[N], vis[N];
int up[N];
int m;

void dfs(int u, int pre) {
	vis[u] = stk[u] = 1;
	dp[u][1] = dp[u][3] = 1, dp[u][0] = dp[u][2] = 0;
	for (auto i : v[u])
		if (!vis[i]) {
			dfs(i, u);
			if (up[i] != 0 && up[i] != u)
				up[u] = up[i];
			if (u == up[i]) {
				dp[u][0] += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));
				dp[u][1] += dp[i][0];
				dp[u][2] += max(max(dp[i][0], dp[i][1]), max(dp[i][2], dp[i][3]));//很容易发现,对于一个环结束的时候,可以取 dp[2] 和 dp[0] 的增量相同,dp[3] 和 dp[1] 的增量相同
				dp[u][3] += dp[i][0];
			} else {
				dp[u][0] += max(dp[i][0], dp[i][1]);
				dp[u][1] += dp[i][0];
				dp[u][2] += max(dp[i][2], dp[i][3]);
				dp[u][3] += dp[i][2];
			}
		} else if (i != pre && stk[i])
			up[u] = i, dp[u][1] = dp[u][2] = -1e16;
	stk[u] = 0;
}

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		v[x].push_back(y), v[y].push_back(x);
	}
	dfs(1, 0);
	cout << max(dp[1][0], dp[1][1]) << endl;
	return 0;
}

可以发现,这份代码和骑士那道题的那份代码是差不多了,主要改动就是把原来的单个元素 u p up up 变成了一个数组 up[]

最大独立集时间复杂度

学了这么多独立集,来总结一下各种图的求最大独立集的时间复杂度。

首先对于一般图,求独立集属于 NP 完全问题,也就是只能暴力枚举。

对于二分图,设点数 n n n,边数 m m m,则可以使用网络流将复杂度变成 O ( m n ) O(m \sqrt n) O(mn ) 的级别(网络流还没学过qwq)。

对于仙人掌图,也包含了树和基环树,可以使用深搜树 + dp 的方式把复杂度变成 O ( n + m ) O(n+m) O(n+m) 的级别。

对仙人掌图进行缩点

发现仙人掌是一堆环通过一堆边拼在一起,两两之间彼此不相交。显然会发现,这个时候若把每一个环都看作是一个点,那么最终就会变成一棵树,在上面可以跑各种各样的科技。

那么怎么看作是一个点呢???通过 P5236 的圆方树做法,我们想到了可以配合点双连通分量进行缩点。

考虑仙人掌图中的每一个点双,不难发现是这样子的:

  • 每一个点双恰好是一个简单环,或者是恰为一条非环边。

显然简单环一定是极大的点双连通分量,但是剩下的边中的每一条边也会变成一个点双连通分量,所以上面的那句话是正确的。

例如这个仙人掌图的深搜树,树边用实线,回边用虚线:

所有的点双连通分量现在已经用彩色线圈出来了,在旁边写上了新的编号。

最终得到的园方树如下:

以前我们就知道圆方树有着求必经点和可经点的作用,但是在对仙人掌图缩点的时候它会有更大的作用。

观察绿色点双连通分量,发现其 u p up up 结点为 3 3 3 号结点(这里 u p up up 结点为点双连通分量最高的结点,而 d n dn dn 结点为点双连通分量最低的结点),而对应到圆方树里面,发现 3 3 3 就是其父亲!

整理一下可得:

新性质:圆方树里方点的父亲恰好是深搜树中其对应的点双里最高的点。


考虑另一件事情:如何处理环上两点之间的最短距离。

不妨在圆方树里面,针对 14 14 14 号方点进行举例,即原深搜树中的绿色点双连通分量的部分。后面的抽象文字如果有不懂的可以对照着图片想想为什么。

因为获取距离的方法较为套路,这里就简要讲讲思考在这个方法的过程。

发现一个环一定是一条链加上一条回边,这是不由分说地。而且还可以发现两点之间的距离要么是在这条构成环的链上的距离 (也就是直接从深度小的点通过走链走到深度大的点),要么是从另一个方向过来的距离 (也就是先从深度小的点走到 u p up up,然后再走 d n dn dn,再有 d n dn dn 走到深度较大的点)

显然两点之间的最短距离就是上面两片粗体字得到答案的 min ⁡ \min min 值。但是这两个答案太难算了,有没有一些突破口呢?

显然是有的,因为我们可以发现第一托路径的答案 + + + 第二托路径的答案 = = = 整个环的路径权值和。

那么就可以通过路径权值和来快速通过前者得到后者。而且路径权值和是一个定值,因为其他地方没地方放了,就直接把环里面的路径权值和作为这个环对应的方点的点权即可。

而对于前面提到的第一托可能的路径,可以使用在链上 u p up up 到这个点的距离来记录。这样就做完了。最终还有每一个方点到其 u p up up 的距离设为 0 0 0。

总结一下:

  • 方点到其 u p up up 的边权设为 0 0 0。

  • 圆点到方点的边权,设为在深搜树中方点的 u p up up 到圆点的链上距离。

  • 将方点的点权设为环内所有边权的总和。

相关推荐
眼镜哥(with glasses)1 小时前
0527漏洞原理:XSS笔记
运维·笔记·自动化
杏仁海棠花饼1 小时前
杏仁海棠花饼的学习日记第十四天CSS
前端·css·学习
Yurko131 小时前
【C语言】函数指针及其应用
c语言·开发语言·学习
明月清了个风2 小时前
数据结构与算法学习笔记(Acwing 提高课)----动态规划·区间DP
笔记·学习·动态规划
萌新小码农‍2 小时前
Spring框架学习day3--Spring数据访问层管理(IOC)
java·学习·spring
qq_336411752 小时前
【笔记】Trae+Andrioid Studio+Kotlin开发安卓WebView应用
android·笔记·kotlin
woho7788993 小时前
伊吖学C笔记(4、循环、自定义函数、二级菜单)
c语言·开发语言·笔记
hu55667984 小时前
Astra学习之-如何修改Header-logo和favicon图标
学习·astra
ruxue.feng5 小时前
《胜算》
笔记
cui_win5 小时前
LangChain 和 LangChain-ChatChat 的区别
学习·ai·langchain