4+ 图论高级算法

强连通分量

基础概念

强连通:在有向图 GGG 中,如果两个点 uuuvvv 是互相可达的,即从 uuu 出发可以到达 vvv , 从 vvv 也可以到达 uuu , 则称 uuuvvv 是强连通的。如果 GGG 中任意两个点都是互相可达的,则称 GGG 是强连通图。

强连通分量:如果一个有向图 GGG 不是强连通图,那么可以把它分成多个子图, 其中每个子图的内部是强连通的, 而且这些子图已经扩展到最大,不能与子图外的任意点强连通, 称这样的一个 “极大强连通” 子图是 GGG 的一个强连通分量。

DFS生成树

SCC(强连通分量) 中的 tarjan 算法主要是靠 DFS生成树 实现的, 所以接下来介绍DFS生成树:
在这里插入图片描述

有向图的 DFS 生成树主要有 4 种边:

  1. 树边 :示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
  2. 反祖边 :示意图中以红色边表示(即 7 → 1),也被叫做回边,即指向祖先结点的边。
  3. 横叉边 :示意图中以蓝色边表示(即 9 → 7),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先。
  4. 前向边 :示意图中以绿色边表示(即 3 → 6),它是在搜索的时候遇到子树中的结点的时候形成的。

接下来我们考虑 DFS 生成树与强连通分量之间的关系。

如果结点 uuu 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 uuu 为根的子树中。结点 uuu 被称为这个强连通分量的根。

反证法:假设有个结点 vvv 在该强连通分量中但是不在以 uuu 为根的子树中,那么 uuuvvv 的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和 vvv 不在以 uuu 为根的子树中矛盾了。得证。

为什么这两种边都要求“指向的结点已经被访问过了”?

  • 反祖边

    • 定义:指向祖先结点的边。
    • 为什么要求已访问? 祖先节点的定义就是在DFS过程中比当前节点更早被访问的节点。你要指回你的祖先,那个祖先肯定在你之前就被发现了。所以,当DFS走到当前节点,准备沿着反祖边走去时,它指向的那个祖先节点肯定已经被访问过了
  • 横叉边

    • 定义:指向一个既不是祖先也不是后代,但已经被访问过的节点的边。
    • 为什么要求已访问? 这是横叉边的核心定义!横叉边连接的是DFS树中两个不存在祖先-后代关系的分支。当你从一个分支走到另一个分支时,另一个分支的节点必然是在之前某次DFS过程中已经被探索过的。如果那个节点还没被访问,DFS就会把它作为当前节点的“后代”(形成一条树边),而不是一条横叉边。

tarjan相关概念

dfn[u]: 时间戳,DFS遍历时结点 uuu 被搜索的次序。
low[u]: uuuuuu 的子树里返祖边横插边能连到还没出栈的 dfndfndfn 最小的点。

按照DFS遍历次序对图中所有的结点进行搜索,维护每个结点的 dfndfndfnlowlowlow 变量,且让搜索到的结点入栈。每当找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。在搜索过程中,对于结点 u 和与其相邻的结点 vv 不是 u 的父节点)考虑 3 种情况:

1. vvv 未被访问:继续对 vvv 进行深度搜索。在回溯过程中,用 lowvlow_vlowv 更新 lowulow_ulowu 。因为存在从 uuuvvv 的直接路径,所以 vvv 能够回溯到的已经在栈中的结点,uuu 也一定能够回溯到。
2. vvv 被访问过,已经在栈中:根据 low 值的定义,用 dfnvdfn_vdfnv 更新 lowulow_ulowu
3. vvv 被访问过,已不在栈中:说明 vvv 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 uuu 使得 dfnu=lowu\textit{dfn}_u=\textit{low}_udfnu=lowu 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 dfndfndfnlowlowlow 值最小,不会被该连通分量中的其他结点所影响。

因此,在回溯的过程中,判定 dfnu=lowu\textit{dfn}_u=\textit{low}_udfnu=lowu 是否成立,如果成立,则栈中 uuu 及其上方的结点构成一个 SCC。

在这里插入图片描述

void tarjan(int u) {dfn[u] = low[u] = ++tim;stk.push_back(u), in_stk[u] = true;for (auto y : e[u]) {if (dfn[y] == -1) {tarjan(y);low[u] = std::min(low[u], low[y]);} else if (in_stk[y]) {low[u] = std::min(low[u], dfn[y]);}}if (low[u] == dfn[u]) {++scc_cnt;int y;do {y = stk.back();stk.pop_back();in_stk[y] = false;id[y] = scc_cnt;siz[scc_cnt]++;}while(y != u);}}

缩点之后,我们的图就变成了一个DAG(有向无环图),我们就可以利用有向无环图来做很多的事情,比如拓扑排序、DP等。

Template

struct SCC {int n, tim, scc_cnt;std::vector<std::vector<int>> e; std::vector<int> stk;std::vector<int> dfn, low, id;//各个点的时间戳dfn, 各个点能走到的最小的时间戳low, 对应的强连通分量id值std::vector<bool> in_stk;std::vector<int> siz;SCC() {}SCC(int _n) {init(_n);}void init(int _n) {n = _n;e.assign(n, {});dfn.assign(n, -1);in_stk.assign(n, false);low.resize(n);id.assign(n, -1);stk.clear();siz.assign(n, 0);tim = scc_cnt = 0;}void addEdge(int u, int v) { e[u].push_back(v);}void tarjan(int u) {dfn[u] = low[u] = ++tim;stk.push_back(u), in_stk[u] = true;for (auto y : e[u]) {if (dfn[y] == -1) {tarjan(y);low[u] = std::min(low[u], low[y]);} else if (in_stk[y]) {low[u] = std::min(low[u], dfn[y]);}}if (low[u] == dfn[u]) {++scc_cnt;int y;do {y = stk.back();stk.pop_back();in_stk[y] = false;id[y] = scc_cnt;siz[scc_cnt]++;}while(y != u);}}std::vector<int> work() {for (int i = 1;i <= n; i++) {if (dfn[i] == -1) {tarjan(i);}}return id;}};

2-SAT

背景

假设有 nnn 对夫妻被邀请参加一个聚会,每对夫妻中只有一人可以列席。在 2×n2 \times n2×n 个人中,某些人(不包括夫妻)之间有着很大的矛盾,有矛盾的两个人不会同时出现在聚会上,有没有可能让 nnn 个人同时列席 ?

现在让我们思考这个问题,如果我们没有学过相关算法,我们大概会这样做:每对夫妻都枚举一个人然后一个一个试,时间复杂度高达 O(2n)O\left( 2^{n}\right)O(2n)

定义

2-SAT,简单的说就是给出 nnn 个集合,每个集合有两个元素,已知若干个 ⟨a,b⟩\langle a,b \ranglea,b ,表示 aaabbb 矛盾(其中 aaabbb 属于不同的集合)。然后从每个集合选择一个元素,判断能否一共选 nnn 个两两不矛盾的元素。显然可能有多种选择方案,一般题中只需要求出一种即可。

解决方法

针对2-SAT问题,我们可以选择使用SCC + 拓扑排序来解决。

第一步,我们要开始建图:
我们利用矛盾关系来建图,比如 AAABBB 矛盾,A‾\overline{A}AB‾\overline{B}B 矛盾,这就意味着 AAA 出现的时候, B‾\overline{B}B 就一定要出现, 同理,BBB 出现的时候 A‾\overline{A}A 也一定要出现, 这个时候我们就可以用 AAA 指向 B‾\overline{B}B , 然后 BBB 指向 A‾\overline{A}A

在这里插入图片描述
第二步,我们应该如何判断是否有解呢 ?
当我们建成一个有向图之后,根据SCC的定义,每个SCC里面各个点都是相互依赖的(一个点出现,其他点都要出现),如果一个人和他的对立面(比如 AAAA‾\overline{A}A )同时出现在一个SCC,则证明无解。

第三步,证明有解之后我们该怎么办呢?
我们先缩点,缩完点之后整张图就变成了一张DAG, 然后我们思考,我们建图时指向的就是被依赖的,那么我们肯定就是优先选择被依赖的更多的,那么其实就是一个逆向的拓扑排序, 不过实际上我们并不需要真正的做拓扑排序,因为SCC编号就是逆向的拓扑排序,SCC编号越小,出现优先级越高。

时间复杂度为 O(n+m)O(n + m)O(n+m)

例题

在这里插入图片描述

基础思路

假设 xxx 为 0, yyy 为 1 为 一个条件,那么 xxx 为 1 时,yyy 就必然需要为 1,yyy 为 0 时,xxx 就必然需要为 0,依赖关系已经形成。

代码模板

#include <bits/stdc++.h>using ll = long long;#ifndef DEBUG
#define debug(x)
#endifstruct TwoSat {int n;std::vector<std::vector<int>> e;std::vector<bool> ans;TwoSat() {}TwoSat(int _n) {init(_n);}void init(int _n) {n = _n;e.resize(2 * n);ans.resize(n);}void addEdge(int u, int a, int v, int b) {int na = (a ^ 1), nb = (b ^ 1);e[u + na * n].push_back(v + b * n);e[v + nb * n].push_back(u + a * n); }bool satisfiable() {std::vector<int> id(2 * n, -1), dfn(2 * n, -1), low(2 * n, -1);std::vector<int> stk;std::vector<bool> in_stk(2 * n, false);int tim = 0, scc_cnt = 0;std::function<void(int)> tarjan = [&](int u) {dfn[u] = low[u] = ++tim;stk.push_back(u), in_stk[u] = true;for (auto v: e[u]) {if (dfn[v] == -1) {tarjan(v);low[u] = std::min(low[u], low[v]);} else if (in_stk[v]) {low[u] = std::min(low[u], dfn[v]);}}if (dfn[u] == low[u]) {scc_cnt++;int y;do {y = stk.back();stk.pop_back();in_stk[y] = false;id[y] = scc_cnt;} while (y != u);}}; for (int i = 0;i < 2 * n; i++) {if (dfn[i] == -1) {tarjan(i);}}for (int i = 0;i < n;i++) {if (id[i] == id[i + n]) {return false;}ans[i] = id[i] > id[i + n];}return true;}std::vector<bool> answer() {return ans;}
};int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n, m;std::cin >> n >> m;TwoSat g(n);for (int i = 1;i <= m; i++) {int x, a, y, b;std::cin >> x >> a >> y >> b;x--;y--;g.addEdge(x, a, y, b);} if (g.satisfiable()) {std::vector<bool> ans = g.answer();std::cout << "POSSIBLE\n";for (int i = 0;i < n; i++) {if (ans[i]) {std::cout << 1 << " ";} else {std::cout << 0 << " ";}}} else {std::cout << "IMPOSSIBLE\n";}return 0;
}

差分约束

差分约束系统 是一种特殊的 nnn 元一次不等式组,它包含 nnn 个变量 x1,x2,…,xnx_1,x_2,\dots,x_nx1,x2,,xn 以及 mmm 个约束条件,每个约束条件是由两个其中的变量做差构成的,形如 xi−xj≤ckx_i-x_j\leq c_kxixjck,其中 1≤i,j≤n,i≠j,1≤k≤m1 \leq i, j \leq n, i \neq j, 1 \leq k \leq m1i,jn,i=j,1km 并且 ckc_kck 是常数(可以是非负数,也可以是负数)。我们要解决的问题是:求一组解 x1=a1,x2=a2,…,xn=anx_1=a_1,x_2=a_2,\dots,x_n=a_nx1=a1,x2=a2,,xn=an,使得所有的约束条件得到满足,否则判断出无解。

差分约束系统中的每个约束条件 xi−xj≤ckx_i-x_j\leq c_kxixjck 都可以变形成 xi≤xj+ckx_i\leq x_j+c_kxixj+ck,这与单源最短路中的三角形不等式 dist[y]≤dist[x]+z\mathit{dist}[y]\leq \mathit{dist}[x]+zdist[y]dist[x]+z 非常相似。因此,我们可以把每个变量 xix_ixi 看做图中的一个结点,对于每个约束条件 xi−xj≤ckx_i-x_j\leq c_kxixjck,从结点 jjj 向结点 iii 连一条长度为 ckc_kck 的有向边。

注意到,如果 {a1,a2,…,an}\{a_1,a_2,\dots,a_n\}{a1,a2,,an} 是该差分约束系统的一组解,那么对于任意的常数 ddd{a1+d,a2+d,…,an+d}\{a_1+d,a_2+d,\dots,a_n+d\}{a1+d,a2+d,,an+d} 显然也是该差分约束系统的一组解,因为这样做差后 ddd 刚好被消掉。

算法流程

dist[0]=0\mathit{dist}[0]=0dist[0]=0 并向每一个点连一条权重为 000 的边,跑单源最短路,若图中存在负环,则给定的差分约束系统无解,否则,xi=dist[i]x_i=\mathit{dist}[i]xi=dist[i] 为该差分约束系统的一组解。

例题

在这里插入图片描述

#include <bits/stdc++.h>using ll = long long;#ifndef DEBUG
#define debug(x)
#endifconstexpr int inf = 2e9;int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n, m;std::cin >> n >> m;std::vector<std::vector<std::pair<int, int>>> adj(n + 1);for (int i = 0;i < m; i++) {int u, v, c;std::cin >> u >> v >> c;adj[v].push_back({u, c});}for (int i = 1;i <= n; i++) {adj[0].push_back({i, 0});}std::queue<int> q;std::vector<int> cnt(n + 1, 0), w(n + 1, inf);std::vector<bool> st(n + 1, false);q.push(0);w[0] = 0;while (!q.empty()) {auto u = q.front();q.pop();st[u] = false;for (auto [v, c]: adj[u]) {if (w[v] > w[u] + c) {w[v] = w[u] + c;if (!st[v]) {q.push(v);cnt[v]++;if (cnt[v] > n) {std::cout << "NO\n";return 0;}st[v] = true;}}}}for (int i = 1;i <= n; i++) {std::cout << w[i] << " ";}return 0;
}

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

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

相关文章

从罗永浩访谈李想中学习现代家庭教育智慧

引言 在这个信息爆炸的时代&#xff0c;每个父母都在寻找培养孩子的最佳方式。在罗永浩与理想汽车创始人李想的深度访谈中&#xff0c;我们看到了一个成功企业家童年成长的真实样本。李想的成长经历为现代家庭教育提供了许多值得深思的启示。 一、正义感与乐观精神的种子 李想回…

AI实现超级客户端打印 支持APP 网页 小程序 调用本地客户端打印

核心思路都是&#xff1a;需要一个安装在用户电脑上的“中间人”程序&#xff08;本地客户端&#xff09;来接管打印任务&#xff0c;然后通过某种通信方式命令这个客户端进行打印。下面我将分平台详细阐述各种实现思路、优缺点和适用场景。一、核心思路与公共组件&#xff1a;…

Java集合(Collection、Map、转换)

✅ 推荐使用 ❌ 已过时 1. Collection Collection 是集合框架的根接口之一&#xff0c;它是所有单列集合&#xff08;如 List、Set、Queue 等&#xff09;的公共父接口。Collection 接口定义了集合的基本操作&#xff0c;比如添加、删除、遍历等。 Collection ├── List │ …

全国网络安全知识竞赛有哪些

全国范围内有多种类型的网络安全知识竞赛&#xff0c;涵盖国家级、行业级、高校、青少年和企业等多个维度。以下是主要的网络安全知识竞赛分类及详细介绍&#xff1a;一、国家级网络安全竞赛"强网杯"全国网络安全挑战赛主办单位&#xff1a;中央网信办、河南省人民政…

系统架构设计师备考第1天——系统架构概述

一、架构本质与角色定位架构 系统的骨架 ✅ 核心作用&#xff1a; 决定系统的健壮性、生命周期、扩展性衔接需求与实现&#xff0c;保障早期质量 &#x1f468;&#x1f4bb; 架构师核心能力&#xff1a;能力维度具体要求技术掌控力精通基础技术&#xff0c;洞悉局部瓶颈决策设…

c#实现鼠标mousemove事件抽稀,避免大数据阻塞网络

这个封装类可以独立于具体的网络传输逻辑&#xff0c;为任何需要减少鼠标移动数据量的应用提供灵敏度和数据量优化。 核心优化功能 1. 灵敏度调整 // 减少微小移动的数据发送 (2, 1) 0.5 → (1, 0) // 忽略微小移动2. 移动累积 // 累积多次小移动&#xff0c;批量发送 (1, 0) …

机器学习 [白板推导](十三)[条件随机场]

​ 17. 条件随机场&#xff08;Conditional Random Field&#xff0c;CRF&#xff09; 17.1. 背景 机器学习分类模型中&#xff0c;有硬分类和软分类两种主流思想&#xff0c;其中硬分类模型有支持向量机SVM&#xff08;最大化几何间隔&#xff09;、感知机PLA&#xff08;误…

调味品生产过程优化中Ethernet/IP转ProfiNet协议下施耐德 PLC 与欧姆龙 PLC 的关键通信协同案例

案例背景在食品饮料行业&#xff0c;生产过程的精准控制对于保证产品质量和安全至关重要。某知名食品饮料企业的生产线上&#xff0c;前处理、灌装和包装环节采用了基于 ProfiNet 主站的施耐德 M340 系列 PLC 进行控制&#xff0c;以确保生产过程的稳定性和精确性。而原料仓储和…

Elasticsearch vs 单表LIKE查询性能对比

关键因素影响 1、索引结构&#xff1a; .Elasticsearch使用倒排索引&#xff0c;特别适合文本搜索 .传统数据库即使有索引&#xff0c;对LIKE %keyword%这种模式也无法有效利用 2、查询复杂度&#xff1a; .简单查询&#xff1a;ES快5-10倍 .复杂组合查询&#xff1a;ES可能快1…

如何通过WordPress联盟营销获取潜在客户

您是否经营着一个销售周期较长的业务&#xff1f; 那么你就会知道&#xff0c;从首次访问者那里获得立即销售的机会是很少见的。 当然&#xff0c;您的潜在客户在进行重大投资之前需要时间进行研究、比较各种方案并建立信任。这时&#xff0c;联盟营销线索挖掘就成为您的秘密…

git实战(8)git高阶命令分析【结合使用场景】

以下是 Git 高阶命令分享&#xff0c;涵盖高效协作、历史重构、问题排查等场景&#xff0c;助你成为 Git 高手&#xff1a; 一、历史重构与清理 1. 交互式变基&#xff08;改写历史&#xff09; git rebase -i HEAD~3 # 修改最近3次提交操作选项&#xff1a; reword&#xff1…

生成一个竖直放置的div,宽度是350px,上面是标题固定高度50px,下面是自适应高度的div,且有滚动条

<!-- 我要生成一个竖直放置的div&#xff0c;宽度是350px&#xff0c;上面是标题固定高度50px&#xff0c;下面是自适应高度的div&#xff0c;且有滚动条。 --><style>html,body{/* height:100vh; */margin:10px; padding:10px;} </style><div style"…

题解:P13754 【MX-X17-T3】Distraction_逆序对_前缀和_Ad-hoc_算法竞赛C++

Beginning 这道题思维难度很大&#xff0c;有两个难点其实都不好解决&#xff0c;但因为其代码太过弱智所以只是绿题。 本题解详细地分析了做题时的历程与思路&#xff0c;所以希望大家可以仔细地完整阅读。 Analysis 首先先大体观察一下题目的性质&#xff1a;nnn 是排列&…

Android Studio下载gradle文件很慢的捷径之路

小伙伴们是不是也经常遇到导入新的项目时&#xff0c;AS一直卡在gradle的下载中。下面介绍一种简单暴力的方式来处理这个问题。 首先我们到gradle的官网下载自己想要的gradle版本。我这里以gradle7.5为例。点击下载gradle-7.5-bin.zip的压缩包。下载完成后无需解压。直接到C:\U…

【C++】全局变量/静态变量的初始化时机

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、全局变量下断点调试1. int a 10; —— 不能卡住断点2. static int b; —— 不能卡住断点3. MyClass c; —— 可以卡住断点4. static MyClass d; —— 可以卡住断…

水体反光 + 遮挡难题破解!陌讯多模态融合算法在智慧水务的实测优化

一、智慧水务行业检测痛点&#xff08;数据支撑 场景难点&#xff09; 根据《2023 年中国智慧水务发展报告》&#xff0c;当前水务监控系统在核心业务场景中面临两大效率瓶颈&#xff0c;直接影响水厂运维与供水安全&#xff1a; 高误报率导致运维资源浪费&#xff1a;水厂沉…

C++的指针和引用:

目录 引用&#xff1a; 注意&#xff1a; 左值引用和右值引用&#xff1a; 左值引用&#xff1a; 右值引用&#xff1a; 指针&#xff1a; 指针与引用的区别&#xff1a; 引用&#xff1a; 在C中&#xff0c;‌引用‌是一种为已存在变量创建别名的机制&#xff0c;它允…

图像处理中的伪影

目录 一、块效应伪影 / 块状伪影 二、 去除块状伪影 三、振铃伪影 一、块效应伪影 / 块状伪影 块状伪影(Blocking Artefacts)是对经过变换编码的图像进行重建时&#xff0c;图像中可能会出现压缩过程产生的可见伪影。基于块的变换编码中&#xff0c;一种常见伪影是 “块效应…

Java:对象的浅拷贝与深拷贝

目录 一、概念 二、实现方式 2.1 浅拷贝&#xff08;不推荐&#xff09; 2.2 深拷贝 2.2.1 方法一&#xff1a;重写 clone() 方法并递归克隆&#xff08;常用&#xff09; 2.2.2 方法二&#xff1a;通过序列化实现&#xff08;更强大&#xff0c;但更重&#xff09; 2.2…

佰钧成 社招 一面

1. “评估需求、排期”的工作流程&#xff1f; “我的工作流程一般是这样的&#xff1a; 需求评审&#xff1a; 首先会和产品、后端同学一起过需求&#xff0c;确保我完全理解了业务背景和要实现的价值&#xff0c;而不仅仅是功能点。技术方案设计&#xff1a; 之后&#xff0c…