最小树形图小记

news/2025/3/28 10:02:07/文章来源:https://www.cnblogs.com/Sktn0089/p/18785762

参考文章:link

英文简称为 \(\mathtt {DMST}\),即 \(\text {Directed Minimum Spanning Tree}\),也就是求有向图的生成树。

注意这里的生成树特指给定根 \(r\) 的外向树 / 内向树,下文统一默认为内向树。

为什么不能用 Prim

主要因为当前选择扩展的边不一定是最优的。比如已经扩展了点 \(1\),点 \(2,3\) 未扩展。现在有边 \(e_1 = (u = 1, v = 2, w = 3), \ e_2 = (u = 1, v = 3, w = 4), \ e_3 = (u = 2, v = 3, w = 1)\),那么最优方案一定是选择边 \(e_2\)\(e_3\)。按照 Prim 算法的流程第一步应是选 \(e_1\),这样就不优了。根本原因在于选择 \(e_1\) 之后,由于图是有向的,所以不能选 \(e_3\),而如果第一步选了 \(e_2\) 的话就可以选 \(e_3\)

Edmonds Algorithm(朱刘算法)

DMST 问题相当于每个点选择一条出边,满足不成环。

考虑对于每个点 \(u(u \not = r)\),选择一条权值最小的出边,组成一个边集 \(F\)

如果 \(F\) 中没有环,那么已经得到答案。

如果 \(F\) 中存在一个大小为 \(k\) 环,那么有一个结论:最终答案方案中一定包含这 \(k\) 条边中恰好 \(k - 1\) 条。

证明可以考虑调整法,如果包含边数 \(\le k - 2\) 一定可以调整为 \(k - 1\) 条,且答案更优。

我们考虑“反悔”思想:先把这 \(k\) 条边都选了,然后接下来选择一条起点在该环上的一条新边,以替换原来的一条边。

容易想到可以将这个环缩点。不妨设环上点依次为 \(x_1, x_2, \dots, x_k\),对于 \(x_i\),令 \(w\) 为当前环上对应出边的权值,对于其所有终点不在环上的出边 \(e_1, e_2, \dots, e_t\),可以令 \(w_{e_1} \sim w_{e_t}\) 都减去 \(w\)。比如之后如果选择了 \(e_1\),那么其权值会增加 \(w_{e_1}'\),其中 \(w_{e_1}'\) 为减去 \(w\) 后的权值。

不难发现缩完这个环后是和原问题等价的,所以我们可以不断找出新的 \(F\),然后不断缩环,直到 \(F\) 不存在环为止。时间复杂度为 \(\mathcal O(nm)\)

Tarjan Algorithm

我们对原问题加以调整:建立一个虚点 \(r'\),然后添加一条起点为 \(r\),终点为 \(r'\),权值为 \(+ \infty\) 的边。然后依次添加边 \((1, 2), (2, 3), \dots, (n - 1, n), (n, 1)\),这些边的权值都为 \(+ \infty\)

由于我有了虚点 \(r'\) 可以作为新的根,并且 \((r, r')\) 边权太大,几乎不可选,所以我们可以先不考虑 \(r'\),先求出点 \(1\sim n\) 的最小树形图,最后再加入边 \((r, r')\)

注意此时 \(r\) 并不是根,所以 \(r\) 也需要选择一条出边。由于加了 \(n\) 条边,所以点 \(1\sim n\) 组成了一张强连通图。换言之,我们可以通过不断缩环,最后缩成一个点。

所以现在的任务只有一个:不断找环,然后缩点。

由于不同环之间互相独立,不需要按照 Edmonds 算法的顺序来处理,先缩哪个并不影响。

找环需要找边,我们先从初始任意一个点开始,比如 \(x = 1\)。在 Tarjan 算法中,我们需要维护一条链表示已经选择的 \(F\) 中的边集,并实时维护 \(x\) 为当前链头,不断执行以下流程:

  • 找到 \(x\) 的一条权值最小的出边 \((u, v, w)\),找到 \(v\) 所在的(缩完环后对应的)的点 \(y\)

  • 如果 \(x = y\),说明这是一条自环,则删掉这条边,重新找一条边。

  • 否则,如果 \(y\) 不在链中,则将 \((u, v, w)\) 加入 \(F\),然后对于 \(x\) 的每条出边的权值都减去 \(w\) 表示反悔,并直接接上去 \(x \gets y\)

  • 否则,\(y\) 在链中,则找到了一个环。记录每个点的前驱,容易找到环上的点集,将他们缩成一个新的点,并令 \(x\) 为这个新点。

查询一个点所在的缩完后的大点可以用并查集,查询一个点出边最小值并整体减一个数可以用堆,如需合并可以使用左偏树。

不难看出整个算法流程以及最终 DMST 所有边权值之和与 \(r\) 的取值并没有任何关系,这也是我们可利用的一大特点。

接下来我们考虑如何求得 DMST 方案,定义函数 \(\mathtt {solve} (u, r)\),表示在(缩完后的)点 \(u\) 中,求出删掉点 \(r\) 的出边后的方案。

为了方便,设 \(\mathtt {children}(u)\) 表示 \(u\) 由哪些点缩点而来,\(\mathtt {fa}(u)\) 表示 \(u\) 点一次缩点后的编号,这形成了一棵缩点树。

\(u\) 为一个单点,则直接返回。

\(v\)\(u\) 在树上的包含点 \(r\) 的儿子,那么对于 \(x \in \mathtt {children}(u) \backslash v\),令点 \(x\) 的出边为 \((p, q, w)\),则执行 \(\mathtt {solve} (x, p)\)

对于 \(v\),则执行 \(\mathtt {solve} (v, r)\),时间复杂度 \(\mathcal O(n\log m)\)

  • 模板题
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb push_back
#define i128 __int128
using namespace std;
char buf[1 << 22], *p1, *p2;
// #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
template <class T>
const inline void rd(T &x) {char ch; bool neg = 0;while(!isdigit(ch = getchar()))if(ch == '-') neg = 1;x = ch - '0';while(isdigit(ch = getchar()))x = (x << 1) + (x << 3) + ch - '0';if(neg) x = -x;
}
const ll maxn = 2e5 + 10, inf = 1e9, mod = 998244353, iv = mod - mod / 2;
ll power(ll a, ll b = mod - 2, ll p = mod) {ll s = 1;while(b) {if(b & 1) s = 1ll * s * a % p;a = 1ll * a * a % p, b >>= 1;} return s;
}
template <class T, class _T>
const inline ll pls(const T x, const _T y) { return x + y >= mod? x + y - mod : x + y; }
template <class T, class _T>
const inline void add(T &x, const _T y) { x = x + y >= mod? x + y - mod : x + y; }
template <class T, class _T>
const inline void chkmax(T &x, const _T y) { x = x < y? y : x; }
template <class T, class _T>
const inline void chkmin(T &x, const _T y) { x = x < y? x : y; }ll n, m, r, pre[maxn], ans, d[maxn], tot;
ll find(ll x) { return d[x] ^ x? d[x] = find(d[x]) : x; }struct Heap {ll dis[maxn], tag[maxn], val[maxn];ll fr[maxn], tot, lc[maxn], rc[maxn];void pushdown(ll p) {if(lc[p]) tag[lc[p]] += tag[p], val[lc[p]] += tag[p];if(rc[p]) tag[rc[p]] += tag[p], val[rc[p]] += tag[p];tag[p] = 0;}ll merge(ll p, ll q) {if(!p || !q) return p | q;pushdown(p), pushdown(q);if(val[p] > val[q]) swap(p, q);rc[p] = merge(rc[p], q);if(dis[lc[p]] < dis[rc[p]]) swap(lc[p], rc[p]);dis[p] = dis[rc[p]] + 1; return p;}ll Newnode(ll u, ll w) {++tot, fr[tot] = u, val[tot] = w;return tot;}
} tr; ll rt[maxn];int main() {rd(n), rd(m), rd(r); tot = n;for(ll i = 1; i <= m; i++) {ll u, v, w; rd(u), rd(v), rd(w);rt[v] = tr.merge(rt[v], tr.Newnode(u, w));}for(ll i = 1; i <= 2 * n; i++) d[i] = i;for(ll i = 1; i <= n; i++) {ll u = i, v = i % n + 1;rt[v] = tr.merge(rt[v], tr.Newnode(u, inf));}ll x = 1;while(rt[x]) {ll w = tr.val[rt[x]], k = find(tr.fr[rt[x]]);tr.pushdown(rt[x]), pre[x] = k;rt[x] = tr.merge(tr.lc[rt[x]], tr.rc[rt[x]]);if(find(k) == find(x)) continue;if(x != find(r)) ans += w;if(rt[x])tr.val[rt[x]] -= w, tr.tag[rt[x]] -= w;if(pre[k]) {ll f = ++tot;for(; x ^ f; x = find(pre[x])) {d[x] = f;rt[f] = tr.merge(rt[f], rt[x]);} x = f;} else x = k;} printf("%lld\n", ans >= inf? -1ll : ans);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/902625.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

停车场立体车库人员滞留报警系统

停车场立体车库人员滞留报警系统采用AI算法,通过大量真实的场景样本训练后,能够在各种应用场景下及时准确的对场景中发生的人员入侵行为发出告警信息。通过对实时视频图像进行智能分析识别,可实现图像全屏周界防护、划定区域周界防护等功能。前端智能AI预警摄像机设备嵌入AI…

解决:按钮被禁用--Popconfirm 气泡确认框仍然可以弹出来展示

按钮被禁用,仍然可以点击,并弹出提示框 <template><a-popconfirmtitle="Are you sure delete this task?"ok-text="Yes"cancel-text="No"@confirm="confirm"@cancel="cancel"> <!-- 按钮被禁用 -->&l…

服务器发送事件(SSE)在现代Web开发中的关键作用

在快速发展的Web开发领域,采用能够增强用户体验并简化数据流的前沿技术对开发者至关重要。服务器发送事件(SSE)正是其中的一项技术。本指南旨在介绍SSE的概念、关键特性、常见应用场景、一个实际的实现示例,并讲解如何使用Apipost调试这些事件。 什么是SSE? 服务器发送事件…

人员非法进入货梯监控报警摄像头

人员非法进入货梯监控报警摄像头对于厂区车间货梯轿厢有人闯入,有很好的预防效果。现场曾发生过货梯运行有员工误进的事件,对安全监控造成严重威胁,对该位置进行实时监控较为重要。货梯监测人员报警摄像机是嵌入AI人体识别算法,对人体的精确检测、跟踪,实现对人体检测分析…

Apifox 与 Apipost 接口状态自定义功能对比:企业级项目的接口管理优化

在现代企业级项目开发中,接口状态管理不仅仅是项目开发中的一个小模块,而是影响团队协作效率、项目推进进度和合规性的重要因素。特别是在中大型项目中,由于涉及多个协作部门与复杂的业务流程,对接口状态的定义和流转有着更高的灵活性要求。然而,不同工具在接口状态管理功…

Windows 提权-服务_弱注册表权限

本文通过 Google 翻译 Weak Registry Key Permissions – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0 前言 1 搜寻服务的弱权限注册表项1.1 枚举弱权限注册表项:accesschk.exe 1.2 枚举弱权限注册…

【每日一题】20250321

人嘛,反正就是这样。表面上做的跟心里想的,完全是两码事。表里不一,这很正常。【每日一题】 1.(14分) \(\hspace{0.7cm}\)如图所示的平面直角坐标系 \(xOy\),在第 \(\mathrm{I}\) 象限内有平行于 \(y\) 轴的匀强电场,方向沿 \(y\) 轴正方向;在第 \(\mathrm{IV}\) 象限的…

2025最新面试题-场景面试题MQ

RabbitMQ的消息处理模型 RabbitMQ的消息处理模型RabbitMQ 是一个基于 AMQP(Advanced Message Queuing Protocol) 协议的消息队列系统生产者(Producer)交换器(Exchange)路由键(Routing Key)队列(Queue)消费者(Consumer)生产者(Producer)生产者是消息的发送者,负责…

day:26 selenium——滚动条、key类

一.滚动条 代码:window.scrollTo(0,2000) from selenium import webdriver #导入selenium模块中的webdriver from selenium.webdriver.support.select import Select import time dx=webdriver.Chrome() #创建一个驱动谷歌浏览器的对象 dx.get("https://www.jd.c…

day:27 unittest实战梳理

一.将cms写入到unittest框架中 1.main调用所有用例import unittest from selenium import webdriver from time import * class Cms(unittest.TestCase):@classmethoddef setUpClass(cls) -> None:pass@classmethoddef tearDownClass(cls) -> None:passdef s…

Redis问题 - Redis-cli.exe无法连接

Redis-cli.exe无法连接解决:打开redis-sever.exe 再回到redis-cli正常

超时

1接口超时2异常