日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

高阶数据结构

發(fā)布時間:2025/3/19 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高阶数据结构 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

高階數(shù)據(jù)結(jié)構(gòu)

  • 并查集
    • “邊代權(quán)的并查集”
    • 帶“擴(kuò)展域”的并查集
  • 樹狀數(shù)組
    • 查詢前綴和
    • 單點(diǎn)增加
    • 樹狀數(shù)組求逆序?qū)?/li>
    • 樹狀數(shù)組的擴(kuò)展應(yīng)用
      • 區(qū)間修改和區(qū)間查詢
    • 維護(hù)一個01序列
  • 線段樹
    • 區(qū)間最大連續(xù)子段和
    • 維護(hù)長度為n的01串
    • 掃描線
  • 分塊

并查集

并查集是一種可以動態(tài)維護(hù)若干個不重疊的集合。具體來說善長動態(tài)維護(hù)許多具有傳遞性的關(guān)系。同時也可以在一張無向圖中維護(hù)節(jié)點(diǎn)之間的連通性。

既然是高階我們重點(diǎn)來講帶有‘’擴(kuò)展域“和“邊帶權(quán)”的并查集。

“邊代權(quán)的并查集”

在這類題中我們通常要維護(hù)的是集合中點(diǎn)的一些權(quán)值問題。如一個節(jié)點(diǎn)到父節(jié)點(diǎn)距離是x,另一個節(jié)點(diǎn)到父節(jié)點(diǎn)距離為y。那么它們兩個的距離就是abs(x - y)。因此在該類問題上我們用一個數(shù)組d,用d【x】保存節(jié)點(diǎn)x到父節(jié)點(diǎn)fa【x】之間的邊權(quán)。

如何在路徑壓縮的時候更新d數(shù)組呢?
我們回想路徑壓縮的過程。對于d數(shù)組,我們只需要在回溯的時候加上其父節(jié)點(diǎn)的d即可,即

int find(int x){if(x == fa[x])return x;int root = find(fa[x);d[x] += d[fa[x]];return fa[x] = root; }

以下圖為例,我們尋找5的父節(jié)點(diǎn)的時候,root 的值先為 3 ,當(dāng)前層的x為4 。 d[x] += d[fa[x]] ; 由于d[fa[x]] 為0 , 所以d[x] 還是4 。然后到x為5的層數(shù),root 由于 上一層返回值是 fa[x] = root;所以還是3 , 不過由于fa[3] 沒有更新還是4 ,所以d[x] += d[fa[x]]; 后d[x] = 4 + 2 = 6 ,最后 d[5] = 3 。很好我們完成了路徑的壓縮和更新問題。


那合并什么說法呢?
為了方便能得到最全面的情況,以下面為例,假設(shè)現(xiàn)在多了 7 5 20
那么我們?nèi)绾魏喜⒛?#xff1f;我們看圖很容易知道缺 1 和 3 之間的邊 , 同時邊權(quán)也容易知道是 5 。 那么如何能得到一個通用的公式呢。我們先回想一下一般的合并,我們先得到 7 和 5 的父節(jié)點(diǎn)分別為 1 和 3 ,我們讓 1 為 3 的兒子節(jié)點(diǎn)。那么現(xiàn)在我們?nèi)钡氖?1 到 3 的邊的權(quán)值 ,即 d[1]。 對于在一個節(jié)點(diǎn)中的兩個節(jié)點(diǎn)的距離為 abs(d[x] - d[y]),所以,現(xiàn)在20 = d[7] - d[5] ,對于現(xiàn)在以3為根節(jié)點(diǎn)后,而 d[7] = 之前的d[7] + d[1] . 所以我們得到
d[1]=20+d[5]?d[7]d[1] = 20 + d[5] -d[7]d[1]=20+d[5]?d[7] . 加粗的地方請留意,不是 d[x] + d[y] 。

void merge(int x, int y, int z ) {int fx = get(x), fy = get(y);if (fx != fy) {fa[fx] = fy, d[fx] = z + d[y] - d[x];} }

帶“擴(kuò)展域”的并查集

帶權(quán)值的并查集和帶“擴(kuò)展域”的并查集都是為了解決傳遞關(guān)系不止一個的問題。帶“擴(kuò)展域”的并查集是擴(kuò)展了多個域來應(yīng)對多種關(guān)系。也就是說將原本的一個點(diǎn)拆分成幾個點(diǎn)來進(jìn)行處理。具體的話建立看一下 食物鏈 這個經(jīng)典題目。

要正確理解好,就需要將題面上的表示關(guān)系的動作轉(zhuǎn)化為關(guān)系。

就以食物鏈為例,將 一個節(jié)點(diǎn)分為三的目的是,能處理好多對關(guān)系。
這些是以關(guān)系建立起來的域,是關(guān)系
X: 本身域
X+n :捕食域
X + 2n : 天敵域

這是什么意思呢?本身域就是與自己同類的一個集合, x + n :域里構(gòu)成的集合是由x的捕食的節(jié)點(diǎn)組成的。 x + 2n :域里構(gòu)成的集合是由x的天敵的節(jié)點(diǎn)組成的。

對于每一種關(guān)系我們都需要在三個域中建立起相應(yīng)的關(guān)系。
如 x 吃 y ,那么就應(yīng)該有 x 是 y 的天敵 , y 是 x 的 捕食對象。同時由三邊有 , y 的 捕食對象是 x 的天敵。

即, merge(x , y+ 2n) 的含義是 x 和 y + 2n 是同一個集合。
Merge(x + n , y ) ,
Merge(x + 2n , y + n)

下面這個題和食物鏈類似
題目轉(zhuǎn)送們

由于是剪刀石頭布這個比較常見的東西我們就可以更好的來理解擴(kuò)展并查集了。 我們對于其中一個節(jié)點(diǎn)x , 與它同為一個域(集合)的是x本身 , 比它小的我們 用x + n , 那么x + 2n 就表示大于x的關(guān)系的集合。我們通過舉一個例子來說明把。 x > y . 那么 表示什么呢? x 本社應(yīng)該與 y的 大于關(guān)系的域(y+2n)在同一個集合 ,(對于擴(kuò)展域我們有一個小技巧,我們對于每一個域中都要考慮),因此先我們考慮一下 x + n 這個域,這個里邊是比x小的集合,那么在這里y比x小,因此應(yīng)該是y本身這個域與x + n 同在一個集合。我們再來考慮一下 x + 2n ,我們由剪刀石頭布很容易知道 , 比 x 大的 應(yīng)該是哪些與y有小于關(guān)系的點(diǎn)。 在這個問題,還有一個點(diǎn)就是裁判的數(shù)量。我們就枚舉一下裁判是誰 , 當(dāng)且僅當(dāng)我們在不講i這個人的關(guān)系加入其中的時候任然沒有矛盾就說明,這個i是一個裁判。

樹狀數(shù)組

樹狀數(shù)組的基本用途是維護(hù)一個序列的前綴和。首先我們需要理解這個數(shù)組的含義是什么。假設(shè)數(shù)組為c[x] , 這個里邊維護(hù)的值是區(qū)間[x?lowbit(x)+1,x][x-lowbit(x) +1 , x][x?lowbit(x)+1,x]中數(shù)的和。有以下三個性質(zhì):

  • 每一個內(nèi)部節(jié)點(diǎn)c[x]保存以它為根的所有子樹的和。
  • 每一個內(nèi)部節(jié)點(diǎn)c[x]的子節(jié)點(diǎn)個數(shù)等于lowbit(x)的位數(shù)
  • 除樹根外,每一個內(nèi)部節(jié)點(diǎn)c[x]的父節(jié)點(diǎn)為c[x+lowbit(x)]
  • 樹的深度為 O(logn)
  • 查詢前綴和

    對于每一個數(shù)x , 可以將區(qū)間[1,x] 利用二進(jìn)制拆分為log(x)個小區(qū)間。具體來說,好比12 = 1100 , 那么可以分為兩個區(qū)間,[1 , 8 ] , [9 , 12]. 回想c[x] 的定義 , 那么c[12] 存儲的是區(qū)間[9 , 12] 的和,那么還缺[1,8]的什么辦,而這正好是12 - lowbit(12) = 8 , 的c[8]對應(yīng)的區(qū)間。因此我們由如下操作:

    int ask(int x){int sum = 0 ;for( ; x ; x-=lowbit(x)) sum += c[x];return sum; }

    因?yàn)闃涞纳疃葹閘og(n)因此我們就可以在O(logn)的時間內(nèi)求出前綴和了。
    當(dāng)然我們要求[ l ,r ] 區(qū)間內(nèi)數(shù)的和的時候 , 只需要aks(r) - ask(l - 1 )即可。

    單點(diǎn)增加

    就是說我們要對原序列a[i] + y , 同時又要正確的維護(hù)前綴和和。有與包含a[i]的區(qū)間只有c[i]以及其父節(jié)點(diǎn)。由性質(zhì)3我們就可以得到以下代碼:

    void add(int x , int val){for( ; x <=n ; x += lowbit(x)) c[x] +=val;

    初始化樹狀數(shù)組

    樹狀數(shù)組求逆序?qū)?/h2>

    利用樹狀數(shù)組求序列的逆序?qū)€數(shù)是利用在集合a上的數(shù)值進(jìn)行樹狀數(shù)組的技巧。為什么這么說呢?我們對于位置i , 查找后邊比它小的個數(shù)。我們不一定需要詢問具體的a[i]的值,我們只需要查詢,后邊是否有值為[0,a[i] - 1 ] 這個區(qū)間內(nèi)。
    因此我們就可以后序遍歷整個數(shù)組,每一次進(jìn)行一次詢問,之后將a[i]值得個數(shù)加1.在這里樹狀數(shù)組記錄c[x] 記錄的是[x?lowbit(x)+1,x][x-lowbit(x) +1 , x][x?lowbit(x)+1,x]中數(shù)出現(xiàn)過的總次數(shù)。

    代碼:

    int res = 0; for(int i = n ; i ; --i){res += ask(a[i] - 1 );add(a[i], 1); }

    樹狀數(shù)組的擴(kuò)展應(yīng)用

    區(qū)間修改和區(qū)間查詢

  • 對于區(qū)間修改:因?yàn)榫S護(hù)的是前綴和,那么我們就可以利用前綴和的技巧,對于修改一個區(qū)間[l,r] ,我們只需要在l處加上d , 在r+1處減去d即可。將區(qū)間修改轉(zhuǎn)變?yōu)閮蓚€單點(diǎn)修改。具體來說我們維護(hù)的不再是具體的值而是維護(hù)指令(修改)的累積影響.具體來說,我們維護(hù)的這個數(shù)組初始為0,b數(shù)組對于位置x的前綴和就是經(jīng)過這些指令后a[x]添加的值。
    對于詢問第x個數(shù)的值我們只需要 a[x] + ask(x) ;
  • 上面說的是區(qū)間修改后單點(diǎn)詢問,那么如何區(qū)間修改和區(qū)間詢問呢,對于區(qū)間詢問也就是a[1~x]的前綴和如何求,如果直接用上面的,我們也是可以求的,這不過時間復(fù)雜度為上升。我們想想如何優(yōu)化,對于區(qū)間 a[1~x] 整體增加的值為 1 ~ x 沒一個b的前綴和.
  • 維護(hù)一個01序列

    這是一個很經(jīng)典的一類題目,很具有思維性,因此就拿出來講一下。
    題目轉(zhuǎn)送們

    解題思路

    首先我們?nèi)菀字?#xff0c;對于同一個編號,在最后出現(xiàn)的,肯定是放在編號對應(yīng)的位置的。難點(diǎn)在于哪些和它同編號的但是在被插隊(duì)之后往后移動的情況。對于這些編號,我們知道一個是它受限于前邊的編號,即它不可能出現(xiàn)在自己初始編號的前邊,第二個就是后邊與其同一編號的人。它會因?yàn)檫@些人而被一直往后移動。由于該題編號是0開始我們可以在讀入的時候就都加上1.之后考慮維護(hù)一個01序列,起始全是1。然后后序遍歷,對于每一個編號,二分來查看01前綴和恰好為其編號的位置是多少,然后將這個位置add(pos , -1);

    線段樹

    以下是線段樹講得比較好的一篇博客了就不繼續(xù)講了。
    線段樹
    下面我們主要講的是線段樹維護(hù)區(qū)間最大連續(xù)子段和 ,以及維護(hù)區(qū)間的眾數(shù)和掃描線。

    區(qū)間最大連續(xù)子段和

    先以下篇博客,
    區(qū)間最大子段和
    下面就來具體的對博客進(jìn)行補(bǔ)充一下。首先是ls和rs,由于兩者類似因此就講講ls。想想我們在求一個以左端點(diǎn)為起始點(diǎn)的最大連續(xù)字段和會什么求呢?由于線段樹是將該區(qū)間分為了左右兩部分,那么我們想到的一個就是左半邊的ls和左半邊的sum加右半邊的ls(是為了保證區(qū)間的連續(xù)性)。
    而m s msms有三種情況:

    該區(qū)間內(nèi)的ms是左兒子的ms
    該區(qū)間內(nèi)的ms是右兒子的ms
    該區(qū)間內(nèi)的ms是左兒子的rs+右兒子的ls.(也是為了保證區(qū)間的連續(xù)性)

    具體來說我們一個線段樹的節(jié)點(diǎn)維護(hù)了四個信息:區(qū)間和,緊靠左端的最大連續(xù)子段和lmax , 緊靠右端的最大連續(xù)子段和rmax , 以及區(qū)間最大字段和。

    t[u].sum = t[u<<1].sum + t[u<<1|1].sum; t[u].lmax = max(t[u<<1].lmax , t[u<<1].sum + t[u<<1|1].lmax]; t[u]rmax = max(t[u<<1|1].rmax , t[u<<1|1].sum + t[u<<1].rmax]; t[u].dat = max(max(t[u<<1].dat , t[u<<1|1].dat) , t[u<<1].rmax + t[u<<1|1].lmax);

    維護(hù)長度為n的01串

    題目轉(zhuǎn)送們

    與上一題類似的思想因此決定要進(jìn)行總結(jié)一下。
    題目大意:

    然我們動態(tài)的維護(hù)一些區(qū)間,之后詢問是否有連續(xù)為0(即沒有人住)的長度為n,有的話取出開頭最小的編號。

    解題思路:

    • 對于線段樹的題目我們首先需要想的是我們的線段樹維護(hù)的是區(qū)間的什么?且維護(hù)的這個東西要滿足區(qū)間可加性。首先對于每一個區(qū)間我們感興趣的是這個區(qū)間中0字符的長度,因此我們就維護(hù)這個東西。那么如何維護(hù)呢?
      首先,我們類似上題的思想,我們用len 表示這個區(qū)間最長的0子串長度,用ld表示靠近左端點(diǎn)最長的0子串長度,用rd表示靠近右端點(diǎn)最長的0子串長度。這里可能有人會疑惑如果最長的在中間什么辦?這其實(shí)沒有關(guān)系,中間這個部分在拆分為區(qū)間的時候一定會出現(xiàn)在某一個區(qū)間的左端點(diǎn)開始或者右端點(diǎn)開始。因此我們采用左,右兩端一個是可以涵蓋中間的情況,另一個是了可以更好的利用線段樹來進(jìn)行維護(hù)。
    • 那么這個長度是什么得到的呢?這個最大長度我們一般是在左區(qū)間的最大長度,右區(qū)間的最大長度和左區(qū)間的右端點(diǎn)開始的最大長度和右區(qū)間左端點(diǎn)開始的最大長度(滿足連續(xù))三者中取一個最大值。
      講完區(qū)間最大長度我們就來講講,以左右端點(diǎn)開始的最大長度。我我們就以左端點(diǎn)為例。 對于左端點(diǎn),首先我們賦值為其左區(qū)間的ld,當(dāng)左區(qū)間的ld = 最區(qū)間的節(jié)點(diǎn)個數(shù)的時候,說明左區(qū)間是全為0的,那么我們就將右區(qū)間的ld與其合并。
    • 同樣在這里涉及到了區(qū)間修改,因此我們利用lazy標(biāo)記來維護(hù)。我們將lazy標(biāo)記分為兩種,因?yàn)檫@個維護(hù)的是區(qū)間是否被覆蓋的問題(不需要加減值)。例如當(dāng)為1的時候就表明這個區(qū)間為空,那么就讓這個區(qū)間的len = ld = rd = r - l + 1 .因?yàn)槿珵?l,如果為2就表示被覆蓋了那么就都賦值為0.
    • 還有一個難點(diǎn)是

    掃描線

    我們需要知道掃描線維護(hù)的是什么
    假設(shè)有以下矩形我們要求出它們的面積和。

    現(xiàn)在我們用掃描線劃分后的圖形為下

    當(dāng)我們掃到黃顏色的坐標(biāo)的是后我們需要知道打問號的線段的長度是多少,很明顯是前一個矩形的邊長加上第一個的邊長然后減去公共部分。我們要維護(hù)的就是當(dāng)前線段的長度。那么我們要如何維護(hù)呢一個就是直接利用區(qū)間來進(jìn)行維護(hù),也就是魔改線段樹,另一個就是離散化y坐標(biāo)后再進(jìn)行。這里我們講講如何離散后用線段樹維護(hù)能得到正確的線段長度。
    魔改線段樹版本

    首先我們離散化后用來建樹的下標(biāo)是不同縱坐標(biāo)的個數(shù)。

    上面是離散化后的坐標(biāo),不過為了正確維護(hù)線段的長度,我們每一個節(jié)點(diǎn)存儲的長度是 ys[t[p].r + 1] - ys[t[p].l] 也就是區(qū)間 [l,r+1]的和。好處是葉子節(jié)點(diǎn)存儲的就不是0這個無用的信息了,就以 第一個邊長( 6 , 8) 和 (1,7) , 在修改的時候變?yōu)榱?6,7),(1,6)(是便于讀取下標(biāo)從0開始)之后為例,那么它更新后的線段樹如下,

    當(dāng)詢問到我在上邊標(biāo)記黑色的線段的長得的時候,在詢問的時候就會將我在圖中標(biāo)出的藍(lán)色的區(qū)域的長度進(jìn)行累加到根節(jié)點(diǎn),而這就是黑色線段的長度了。

    代碼如下:

    #include<stdio.h> #include<vector> #include<algorithm> #include<cstring> #include<vector> using namespace std;const int N = 1E5 + 10; int n , m; double res; struct segment{double x , y1,y2;int k;bool operator < (const segment & t)const{return x < t.x;} }seg[N*2];struct stree{int l , r , cnt ;double len; }tr[N*8];vector<double>ys;void build(int p , int l , int r){tr[p].l = l , tr[p].r = r;if(l == r){tr[p].cnt = tr[p].len = 0;return ;}int mid = (l + r )>> 1;build(p<<1 , l , mid);build(p<<1|1 , mid+1,r);}// 二分查找一個值對應(yīng)的離散后的值 , 即線段樹中的下標(biāo) int find(double y){return lower_bound(ys.begin() , ys.end() , y) - ys.begin(); }void pushup(int p){//tr[p].r 和 tr[p].l 就是下標(biāo)所有是直接拿來取值if(tr[p].cnt) tr[p].len = ys[tr[p].r + 1] - ys[tr[p].l];else if(tr[p].l == tr[p].r)tr[p].len = 0;else tr[p].len = tr[p<<1].len + tr[p<<1|1].len ; // 如果沒有被覆蓋過肯定可以直接通過左右兩邊求 }void modify(int u , int l , int r, int k){if(l <= tr[u].l &&tr[u].r <= r){tr[u].cnt += k ;pushup(u);return ;}else{int mid = (tr[u].l + tr[u].r) >>1;if(l <= mid) modify(u*2 , l , r , k);if(r > mid) modify(u<< 1 | 1 , l , r ,k);pushup(u);} }int main(){int T = 1;while(scanf("%d",&n) , n ){ys.clear();for(int i = 0 ; i < n ; ++i){double x1 , x2 , y2 ,y1;scanf("%lf%lf%lf%lf",&x1,&y1 , &x2,&y2);ys.push_back(y1) , ys.push_back(y2); // ys 里邊存的是值,到時候去建線段樹用的是元素個數(shù)seg[i*2] = {x1 , y1,y2 , 1} , seg[i<< 1 | 1] = {x2 , y1,y2,-1};}sort(ys.begin() , ys.end());ys.erase(unique(ys.begin() , ys.end()) , ys.end()); // 去重m = ys.size();build(1 , 0 , m - 2); // 那去重后的元素個數(shù)來建立線段樹sort(seg , seg + n*2); //res = 0;for(int i = 0 ; i < 2*n ;){ // 遍歷所有的節(jié)點(diǎn)int j = i ;while(j < 2*n&& seg[i].x == seg[j].x) j ++;if(i) res += tr[1].len * (seg[i].x- seg[i - 1].x); // 根節(jié)點(diǎn)是當(dāng)前區(qū)間的高度?while(i < j){modify(1 , find(seg[i].y1) , find(seg[i].y2) - 1 , seg[i].k);i++;}}printf("Test case #%d\n", T ++ );printf("Total explored area: %.2lf\n\n", res);}return 0; }

    分塊

    博主先咕咕下,需要去訓(xùn)練了。訓(xùn)練完會盡快更新的。

    總結(jié)

    以上是生活随笔為你收集整理的高阶数据结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。