【知识】图论 朱刘算法
朱刘算法:
树形图的定义:
-
以某一个点为根的有向树,被称为 树形图
-
一个有向图,满足无环且每个点的入度为
(除了根节点),被称为 树形图 -
最小树形图:对于所有树形图中,找到一个总权值和最小的树形图,被称为 最小树形图。
最小树形图问题本质上其实就是有向图上的最小生成树问题。 Prim 算法和 Kruskal 算法可以解决无向图上的最小生成树问题。 朱刘算法可以解决有向图上的最小生成树问题。
朱刘算法
朱刘算法是一个迭代算法,每一次迭代:
-
除了根节点,对于每个点,找一下这个点的入边中权值最小的边
-
判断选出的边中是否存在环
-
无环,算法结束
-
有环,进入步骤
-
将所有环缩点,得到新图
,对于每条边:- 环内部的边,删去
- 边的终点
在环内,该边的权值变成原权值减去 在环内的边的权值,即 - 其他边,不变
-
算法结束后,之前每一次迭代选出的所有边的总权值之和就是答案。
证明朱刘算法
-
如果第一次选出的边中不存在环,就意味着当前选出的边满足两个条件,无环且每个点都有一个入边,说明我们选出的是一个树形图,由于每个点选出的边都是所有入边里面权值最小的边,所以一定不可能存在其他方案使得我们选择的边权和更小。
-
如果有环,那么为什么算法还是对的呢?
假设原图是
我们考虑
假设我们现在任意去掉一条边
有了以上两个性质,我们可以进行证明。
假设图
对于左边集合中的图的任何一个环,我们只去掉一条边,然后连上一条新边,由于环已经被我们缩点,那么新边就会连向缩点后的新点,对于新点而言,入边就是唯一的,所以去掉一条边后图中无环且每个点的入度为
反过来,对于右边集合中任意一个树形图,我们找到一个不是根节点的缩点后的点,那么这个点必然存在一个入边,且这个入边必然是原图里的某一条边,且它一定连向缩点后这个点内部的某一个点,我们将这个点对应的内部的边去掉,将这条原图中的边加上。这样可以发现,任给我们一个右边集合的树形图,我们都可以转化成左边集合的一个满足两个性质的树形图。
因此两个集合是相互对应的。
然后看一下数量关系,可以发现左边集合加上了一条环外边
综上所述,我们想求左边集合的最小树形图只需要求右边集合的最小树形图就行了,因此每次图中有环进行的处理是正确的。
每次迭代最多去掉一个点,最多迭代
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 105, M = 10005, INF = 1e9;
int n, m, rt = 1, X[N], Y[N], col, in[N];
int vis[N], id[N], pre[N];
struct E{
int u, v, w;
} e[M];
int inline edmonds() {
int ans = 0;
while (true) {
for (int i = 1; i <= n; i++) in[i] = INF;
memset(vis, 0, sizeof vis);
memset(id, 0, sizeof id);
for (int i = 1; i <= m; i++)
if (e[i].w < in[e[i].v]) in[e[i].v] = e[i].w, pre[e[i].v] = e[i].u;
for (int i = 1; i <= n; i++)
if (in[i] == INF && i != rt) return -1;
col = 0;
for (int i = 1; i <= n; i++) {
if (i == rt) continue;
ans += in[i];
int v = i;
while (!vis[v] && !id[v] && v != rt)
vis[v] = i, v = pre[v];
if (v != rt && vis[v] == i) {
id[v] = ++col;
for (int x = pre[v]; x != v; x = pre[x]) id[x] = col;
}
}
if (!col) break;
for (int i = 1; i <= n; i++) if (!id[i]) id[i] = ++col;
int tot = 0;
for (int i = 1; i <= m; i++) {
int a = id[e[i].u], b = id[e[i].v];
if (a == b) continue;
e[++tot] = (E) { a, b, e[i].w - in[e[i].v] };
}
m = tot, n = col, rt = id[rt];
}
return ans;
}
int main() {
scanf("%d%d%d", &n, &m, &rt);
int tot = 0;
for (int i = 1; i <= m; i++) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
if (b != rt && a != b) e[++tot] = (E) { a, b, c };
}
m = tot;
printf("%d\n", edmonds());
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库