狐假虎威的樹鏈剖分

2021-08-20 18:34:11 字數 3241 閱讀 6074

運算元據結構(線段樹)

結束語

最近在做運輸計畫這道題時,發現要用數鏈剖分,於是就打算學學這個玩意兒。

其實之前一直以為這個東西是個很複雜的東西,可能**看起來都很長。

但是,學了之後,我才發現,這個東西很好懂,而且**之所以很長,也有乙個原因就是它需要使用乙個強大的資料結構——線段樹。線段樹的**其實並不算少,這個,學過的人,都應該知道,沒學過的人,不妨看一下:線段樹

所以,我稱之為狐假虎威。

那麼,下面就讓我為你們揭開數鏈剖分的真實面目。

我們在這裡我們採用邊的結構體和煉表去儲存。

先寫乙個結構體:

struct edge
這就是邊的結構體。

注意:如果next為空,則為0

下面我們再定義所需的變數。

edge edg[maxm];    //邊,maxm是邊的最大值

int head

[maxn]; //鍊錶的頭,maxn是點的最大值,表示對應結點指向的第乙個邊的編號

這就是我們對於圖的表示。

這個是基礎的東西,我們不多說。

下面是數鏈剖分的構造環節,我們先來看看我們需要構造哪些資料。

int siz[maxn];//以該結點為根結點的子樹的結點數

int top

[maxn];//該結點所在重鏈的頭結點

int son[maxn];//該結點的重兒子(初始值-1)

int dep[maxn];//該結點深度

int faz[maxn];//該結點的父結點(初始值-1)

int tid[maxn];//該結點的dfs序

int rnk[maxn];//dfs序對應的結點編號

int w[maxn];//點的權值

你可能一下就看懵了,這些都是什麼意思啊?

別急,下面我來解釋一下概念:

其他的,至於什麼是dfs序,什麼是深度,大家應該知道。(如果不知道,那麼少年(或妹子),我勸你先去學一學基礎)

接下來我們就要構造這些陣列了。

void dfs1(int u,int father,int depth)}}

}

中間我對於邊上權值進行了乙個轉換,如果題目給的就是點權值,那麼就需要轉換了,但如果題目給的邊權值,那麼我們就轉換成點權值,也就是將每一條邊的權值放在兒子上,而不是放在父親上。(自己想想為什麼)

這個過程是很好理解的,我不過多解釋了。

這次dfs的目的,就是構造剩下的三個陣列,即top、tid、rnk。

**如下:

void dfs2(int u,int t)

dfs2(son[u],t);//遍歷重兒子

for(int i=head[u];i;i=edg[i].next)

}}

也很好理解,不多說,只說乙個注意點,那就是我們一定是先遍歷重兒子,再遍歷輕兒子,這個大家自己先想想為什麼。(畫個圖就知道了,和線段樹有關,講線段樹的時候再講)

我們這裡只進行兩種操作,更多的操作還要靠各位自己去舉一反三。

我們提供的兩個操作是兩個最基本也是最經典的操作——查詢x到y的距離、給x到y的每乙個邊的權值加上z。

其實就是線段樹的區間修改和區間和查詢。

當時我們先講講數鏈剖分這一塊的操作。

學過的人都知道,就是乙個lca(最近公共祖先)。

思路是,每一次,將深度大的乙個結點,運用重鏈頭結點向上調整,最後找到公共祖先。

**如下:

//查詢x到y的最短路徑長度 

int query_path(int x,int y)else

fx=top[x],fy=top[y];//取當前兩個結點的鏈頭

} if(x!=y)else

}else

return ans;

}

這裡調整還是比較清晰的,但是可能有的人會有乙個疑問,就是調整為父親的時候,會不會調整出根節點的父親?

其實是不會的,因為我們每次都調整深度更深的那乙個,所以不可能是根節點,這樣就不會取到根節點的父結點。

這是經典的修改操作。

**和查詢驚人的相似。

**如下:

void update_path(int x,int y,int z)else

fx=top[x],fy=top[y];

}if(x!=y)else

}else

}

好像沒什麼可講的,和查詢差不多。

接下來要講數鏈剖分的資料結構基礎。

當然不見得都要用線段樹,你也可以用樹狀陣列、splay之類的資料結構,這裡用線段樹做乙個例子,如果你沒學過線段樹,你可以看看這篇寫得不錯的文章:線段樹

這次不直接放**,我們先講講之前留下的那個問題。

那就是:為什麼先遍歷重兒子?

那麼請你拿出筆和紙,畫一棵樹,然後先描出重鏈,然後按優先遍歷重兒子,給它們標上dfs序,然後,你再模擬一下我們的查詢過程。

你有什麼發現?

每次求和的幾個點的dfs序都是連續的。這樣的話我們就能用各種資料結構去實現。(因為我們的資料結構基本上都是求區間和,很少跳躍性求和的)

下面奉上全套線段樹**:

void update(int rt)

void color(int l,int r,int rt,int a)

void push_col(int l,int r,int rt)

}void build(int l,int r,int rt)

int m=(l+r)>>1;

build(l,m,rt<<1);

build(m+1,r,rt<<1|1);

update(rt);

}void modify(int l,int r,int rt,int nowl,int nowr,int c)

push_col(l,r,rt);

int m=(l+r)>>1;

if (nowl<=m) modify(l,m,rt<<1,nowl,nowr,c);

if (m1,r,rt<<1|1,nowl,nowr,c);

update(rt);

}int query(int l,int r,int rt,int nowl,int nowr)

注意這裡的sum和add需要四倍的空間。

最後嘮叨一句,它的時間複雜度是o(nlogn)。

樹鏈剖分 樹鏈剖分講解

好了,這樣我們就成功解決了對樹上修改查詢邊權或點的問題。下面放上 vector v maxn int size maxn dep maxn val maxn id maxn hson maxn top maxn fa maxn 定義 int edge 1,num 1 struct tree e ma...

演算法入門 樹鏈剖分 輕重鏈剖分

目錄 3.0 求 lca 4.0 利用資料結構維護資訊 5.0 例題 參考資料 資料結構入門 線段樹 發表於 2019 11 28 20 39 dfkuaid 摘要 線段樹的基本 建樹 區間查詢 單點修改 及高階操作 區間修改 單點查詢 區間修改 區間查詢 標記下傳 標記永久化 閱讀全文 樹鏈剖分用...

樹鏈剖分 樹剖換根

這是一道模板題。給定一棵 n 個節點的樹,初始時該樹的根為 1 號節點,每個節點有乙個給定的權值。下面依次進行 m 個操作,操作分為如下五種型別 換根 將乙個指定的節點設定為樹的新根。修改路徑權值 給定兩個節點,將這兩個節點間路徑上的所有節點權值 含這兩個節點 增加乙個給定的值。修改子樹權值 給定乙...