再探帶權并查集

典型例題

Acwing

權值

故名思義,在帶權并查集中,我們需要讓每個節點攜帶一個**“權值”**。
那么這個權值應該是什么呢?其實答案就在并查集當中。
由于在并查集當中我們可以在 O ( 1 ) O(1) O(1) 時間內找到一個節點的根節點,那么我們可以讓這個權值表示為:某個節點到根節點的距離

如何維護權值

首先我們需要一個“懶標記數組 d d d”,至于為什么稱其為“懶標記”,稍后再解釋。這個數組就是用來記錄我們權值的數組。
即, d [ i ] d[i] d[i] 表示 i i i 到根節點的距離。
其次,我們需要在 f i n d find find 函數中做一點手腳。這個也稍后解釋,

懶標記數組和find()

明明就是用來維護權值的數組,為什么我們要稱其為懶標記呢?
試想一下,當我們將以 f b fb fb 為根的集合添加到以 f a fa fa 為根的集合的尾部,我們是需要修改以 f b fb fb 為根的子樹集合里面所有點的 d [ ] d[] d[] 的,是不是想象就可怕呢?
好,現在我們試著正經分析一下,如果我們真的要一次性修改以 f b fb fb 為根的子樹集合中的所有點,有什么辦法可以得到這些點嗎?
貪心的想,我們肯定希望直接找到集合中的所有點,然后修改,但這是不可能的!由于我們在并查集中只能找到根節點,而不能從根節點找孩子節點,所以我們只能遍歷所有點,判斷每一個點所在的集合是否為 f b fb fb,這么做的時間復雜度為 O ( N ) O(N) O(N),如果執行 N N N 次這樣的操作,就是平方級別的復雜度!這肯定無法接受!
但我們又無法回避這個問題,該如何做呢? – 參考線段樹中懶標記的做法
當我們需要修改以 f b fb fb 為根的集合中所有點的權值時,我們只修改該集合根節點 f b fb fb 的權值,然后其余的點我們不做操作!
等我們對集合中的某個點 x x x(不是該集合的根節點) 執行 f i n d find find 操作時, f i n d find find 會找到 x x x 的所有父節點,直到根節點。
我們發現,我們找到了一條從直接從底層節點到根節點的路徑,并且尋找這個路徑的過程是遞歸的(線性)!
既然遞歸的路徑是從底到根,那么回溯時的路徑必然是從根到底,而從根到底的過程就可以找到根的所孩子,這些孩子就是該根所在集合中的子樹節點!
這個時候(回溯),就可以用來修改我們的懶標記,將他們變成正確的值。
并且,從根到底的回溯也能保證答案的正確性,因為當某個節點的根被修改時,它所有的子節點也需要修改,子樹的值依賴于它的根的值,因此保證根的正確性,才能保證底的正確性。
例如,我們讓根節點指向一個新的根節點,那么不僅原來的根節點的 d [ ] d[] d[] 變化了,它的所有子節點的 d [ ] d[] d[] 也需要變化。
最后,還有一個疑問?如果你每次 f i n d ( x ) find(x) find(x) 都會修改 x x x 到樹根的路徑上的 d d d,那么會不會導致一個點被重復多次修改,導致它的 d [ ] d[] d[] 比實際更大呢?
答案是不會的,因為在第一次 f i n d ( x ) find(x) find(x) 之后, x x x 會因為路徑壓縮,直接指向樹的根節點,這樣當下一次再 f i n d ( x ) find(x) find(x) 時,會直接返回 p r e [ x ] pre[x] pre[x],不會涉及到清除懶標記(修正它的 d [ ] d[] d[])這一步。

圖解

IMG
順便解釋一下清除懶標記(修正 d [ ] d[] d[])的公式:d[x] = d[x] + d[pre[x]]
具體含義:一個節點到根節點的距離 = 它到父節點的距離 + 父節點到根節點的距離
因此,在修正一個節點 x x x 時,我們需要先修正它的父節點 p r e [ x ] pre[x] pre[x] 到樹根的 d [ p r e [ x ] ] d[pre[x]] d[pre[x]],這一點從公式也可以清晰的看出
這也就是我們在 f i n d ( x ) find(x) find(x)int root = find(pre[x]); 做的事情,如果寫的更清楚一點,那就是:

int find(int x)
{if(pre[x] == x) return x;  // 原本的findfind(pre[x]);   // 1. 先修正所有祖先節點(父->根)s[x] = s[x] + s[pre[x]];    // 2. 修正自己return pre[x] = find(pre[x]);  // 原本的find
}

可以發現,不過是比原本的路徑壓縮 f i n d find find 多了兩條語句罷了!如果我們把最后一句return pre[x] = find(pre[x]); 再優化一下,那就是下面的形式,不過該優化對時間效率的提升很小,因為在我們執行 find(pre[x]) 之后, p r e [ x ] pre[x] pre[x] 便已經直接指向根節點 r o o t root root,這樣當我們下次再執行 f i n d ( p r e [ x ] ) find(pre[x]) find(pre[x]) 時,由于已經路徑壓縮過了,實際查找速度是 O ( 1 ) O(1) O(1) 的。

int find(int x)
{if(pre[x] == x) return x;int root = find(pre[x]);   // 1. 先修正所有祖先節點(父->根)s[x] = s[x] + s[pre[x]];    // 2. 修正自己return pre[x] = root;
}

img

代碼

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 30010;int pre[N];
int d[N], s[N];
// s[i] 表示 i 所在集合中點的個數
// d[i] 標識 i 到其集合中根節點的距離(帶有懶標記的權值數組int find(int x)
{// 當 pre[x] == x,即該節點就是集合的根節點時// 不存在歲回溯路徑,也就不需要也不能去除懶標記if(pre[x] == x) return x;// 存在回溯路徑,去除懶標記// 先遞歸,一直找到根節點int root = find(pre[x]);    // 從根節點開始往下回溯d[x] += d[pre[x]];  // d[x]_new = d[x]_old + d[pre[x]];,參考上面的圖 return pre[x] = root;   // 別忘了路徑壓縮
}void merge(int a, int b)
{int fa = find(a), fb = find(b);if(fa == fb)    return ;// 可以合并// 只修改a的根節點fapre[fa] = fb;d[fa] += s[fb];// 修改集合大小s[fb] += s[fa];
}int main()
{for(int i = 0; i < N; i ++ ){pre[i] = i;s[i] = 1;d[i] = 0;   // 初始狀態時,自己就是自己的根節點}int T;  cin >> T;while(T -- ){string op;  int a, b;cin >> op >> a >> b;if(op == "M")   merge(a, b);else    {int fa = find(a), fb = find(b);if(fa != fb)    cout << -1 << endl;// 注意a和b可能相等的情況else    cout << max(0, abs(d[a] - d[b]) - 1) << endl;}}return 0;
}

2024/3/11

#include <iostream>
#include <algorithm>
#include <cstring>using namespace std;const int N = 30010;int pre[N], d[N], cnt[N];int find(int x)
{if(pre[x] == x) return x;int root = find(pre[x]);d[x] = d[x] + d[pre[x]];return pre[x] = root;
}int main()
{for(int i = 0; i < N; i ++ )    pre[i] = i, cnt[i] = 1;int q;  cin >> q;while(q -- ){string op;  int a, b;cin >> op >> a >> b;int fa = find(a), fb = find(b);if(op == "M"){if(fa != fb){pre[fa] = fb;d[fa] += cnt[fb];cnt[fb] += cnt[fa];}}else {if(fa != fb)    cout << -1 << endl;else {if(a == b)  cout << 0 << endl;else cout << abs(d[a] - d[b]) - 1 << endl;}}}return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899149.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899149.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899149.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vala編成語言教程-構造函數和析構函數

構造函數 Vala支持兩種略有不同的構造方案&#xff1a;我們將重點討論Java/C#風格的構造方案&#xff0c;另一種是GObject風格的構造方案。 Vala不支持構造函數重載的原因與方法重載不被允許的原因相同&#xff0c;這意味著一個類不能有多個同名構造函數。但這并不構成問題&…

本地部署Stable Diffusion生成爆火的AI圖片

直接上代碼 Mapping("/send") Post public Object send(Body String promptBody) { JSONObject postSend new JSONObject(); System.out.println(promptBody); JSONObject body JSONObject.parseObject(promptBody); List<S…

python爬蟲WASM

WASM 一.WASM簡介 1.1 WASM定義 ? WebAssembly(簡稱wasm)是一個虛擬指令集體系架構(virtual ISA),整體架構包括核心的ISA定義、二進制編碼、程序語義的定義與執行,以及面向不同的嵌入環境(如Web)的應用編程接口(WebAssembly API)。是一種運行在現代網絡瀏覽器中的…

Docker鏡像遷移方案

Docker鏡像遷移方案 文章目錄 Docker鏡像遷移方案一&#xff1a;背景二&#xff1a;操作方式三&#xff1a;異常原因參考&#xff1a; 一&#xff1a;背景 比如機器上已經有先有的容器&#xff0c;但是docker pull的時候是失敗的二&#xff1a;操作方式 1、停止正在運行的容器…

關于跨域問題(本地前端訪問服務器端接口跨域出錯)

問題來源&#xff1a; 當服務器封裝了接口但是本地電腦端前端訪問出現跨域問題。 解決方案&#xff1b; 1、使用ipconfig 查看本地電腦的ip地址 ipconfig 2、在后端接口處配置如下代碼 allow_origins["http://本地ip地址:3001", # 局域網內其他設備訪問的本地…

邊緣計算 vs. 云計算,誰才是工業物聯網的未來?

前言 在物聯網&#xff08;IoT&#xff09;飛速發展的今天&#xff0c;邊緣計算正在徹底改變數據的處理、存儲和分析方式。傳統的IoT設備數據通常需要發送到云端進行處理&#xff0c;但隨著設備數量的激增&#xff0c;這種模式在延遲、帶寬和安全性方面暴露出諸多局限。邊緣計…

dell 臺式機 電腦 紐扣電池 如何取下?

dell 臺式機 電腦 紐扣電池 如何取下&#xff1f; 戴爾-optiplex-3060-塔式機-服務手冊

NFC 智能門鎖全棧解決方案:移動端、服務器、Web 管理平臺

目錄 一、系統整體架構 二、移動端 APP 開發 2.1 開發環境與基礎準備 2.2 主要功能模塊 2.3 示例代碼&#xff08;Android/Kotlin 簡化示例&#xff09; 三、后臺服務開發 3.1 環境準備 3.2 主要功能 3.3 示例代碼&#xff08;Node.js Express 簡化示例&#xff09; …

DDR4、DDR5、固態硬盤(SSD)和機械硬盤(HDD)在連續讀/寫、隨機讀/寫性能的對比分析

以下是關于DDR4、DDR5、固態硬盤&#xff08;SSD&#xff09;和機械硬盤&#xff08;HDD&#xff09;在連續讀/寫、隨機讀/寫性能的對比分析&#xff0c;結合技術特性與應用場景的總結&#xff1a; 一、性能對比表格 存儲類型連續讀&#xff08;MB/s&#xff09;連續寫&#x…

【AI】MAC版本本地Stable Diffusion web ui安裝

文章目錄 前言環境依賴homebrewpython3下載stable-diffusion-webui webui模型準備模型網站 中文頁面設置提示詞轉漢語轉英文controlnet安裝controlnet模型下載 結尾 前言 目前&#xff0c;市面上已經出現了很多用Ai 繪圖制作的作品&#xff0c;用于自媒體或者商業等。例如表情…

Linux 云服務器開放端口

首先找到你買服務器的官網&#xff0c;我這里是阿里云 點擊這里的控制臺 這里先點手動添加&#xff0c;再看自己是UDP還是TCP協議&#xff0c;找到對應的協議&#xff0c;目的就填你想開放的端口&#xff0c;源填所有IP/4 0.0.0.0 添加備注點擊保存就開放好了。

[unity 點擊事件] 區域響應點擊事件,排除子節點區域,Raycast Target 應用

當我打開一個二級彈窗后&#xff0c;希望可以通過點擊彈窗以外的區域來關閉該彈窗。一開始我是在彈窗主節點上掛載了一個 button 組件&#xff0c;該 button 注冊的點擊事件中關閉該彈窗。在子節點&#xff08;一個背景圖&#xff09;的image組件上啟用 Raycast Target 選項&am…

表的約束及代碼練習

一.表的約束 查看表&#xff1a;mysql> select * from t_hero; 1.設置t_hero的主鍵為t_id alter table t_hero add primary key(t_id); 2.設置t_hero t_id屬性非空 alter table t_hero modify t_id int not null;3.設置name屬性為非空非重復 alter table t_hero modify…

Linux筆記---動靜態庫(使用篇)

目錄 1. 庫的概念 2. 靜態庫&#xff08;Static Libraries&#xff09; 2.1 靜態庫的制作 2.2 靜態庫的使用 2.2.1 顯式指定庫文件及頭文件路徑 2.2.2 將庫文件安裝到系統目錄 2.2.3 將頭文件安裝到系統目錄 3. 動態庫 3.1 動態庫的制作 3.2 動態庫的使用 3.2.1 顯式…

Java并發編程2(鎖-Sychronized)

目錄 認識Java對象頭 sychronized鎖原理 基本概念 工作原理 1.作用在方法上 2.作用在代碼塊上 工作機制 JVM優化鎖 Monitor鎖 wait/notify park/unpark 線程狀態轉換案例 死鎖 概念 死鎖發生的必要條件 哲學家問題 活鎖 饑餓 概念 饑餓的原因 Reentrant…

現階段高校的人工智能方案培訓如何?

人工智能在未來肯定是核心發展力&#xff0c;核心競爭力&#xff0c;也是國家重點扶持的對象&#xff0c;但我還是不看好高校的人工智能方向&#xff0c;只是怕有些同學對市場前景盲目樂觀&#xff0c;就輕易上車了。 你要是985以上的高校&#xff0c;可以考慮選擇人工智能&…

JavaScript中的繼承有哪些方式?各有什么優缺點

在 JavaScript 中&#xff0c;繼承主要通過原型鏈實現&#xff0c;常見的繼承方式有以下幾種&#xff0c;每種方式都有其優缺點&#xff1a; 1. 原型鏈繼承 1. 實現方式&#xff1a;將子類的原型對象指向父類的實例。 function Parent() {} function Child() {} Child.protot…

深入理解指針(3)(C語言版)

文章目錄 前言 一、字符指針變量二、數組指針變量2.1 數組指針變量是什么2.2 數組指針變量怎么初始化2.2.1 靜態初始化2.2.2 動態初始化 三、二維數組傳參的本質四、函數指針變量4.1 函數指針變量的創建4.2 函數指針變量的使用4.3 typedef關鍵字4.4拓展 五、函數指針數組六、轉…

Linux之 權限提升(Linux Privilege Escalation)

Linux 之權限提升 系統信息 1.獲取操作系統信息 2.檢查PATH&#xff0c;是否有任何可寫的文件夾&#xff1f; 3.檢查環境變量&#xff0c;有任何敏感細節嗎&#xff1f; 4.使用腳本&#xff08;DirtyCow&#xff1f;&#xff09;搜索內核漏洞 5.檢查sudo 版本是否存在漏洞…

【leetcode hot 100 215】數組中的第K個最大元素

解法一&#xff1a;維護最大最小值 -> 堆 -> k個元素的最小值堆 class Solution {public int findKthLargest(int[] nums, int k) {// 維護最大最小值 -> 堆 -> k個元素的最小值堆PriorityQueue<Integer> heap new PriorityQueue<>((n1, n2) -> n…