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 \rangle?a,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_kxi??xj?ck?,其中 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_kxi??xj?ck? 都可以變形成 xi≤xj+ckx_i\leq x_j+c_kxi?xj?+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_kxi??xj?ck?,從結點 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
英文地址,請注明出處:http://en.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…