樹鏈剖分這個演算法是我在 noip2015 day2t3 中學會的乙個優化暴力的演算法,突然感覺這個演算法很吊,然後思路好想,**精簡(bushi,十分符合我的口味,於是就搞來學了一下。
我們知道,對於這樣乙個問題:
在一棵樹上,查詢兩點之間的距離。
這個題目比較簡單,用一遍搜尋去找到每乙個點關於原點那一條鏈的字首,然後最後的答案就是 \(f_u + f_v - 2 \times f_\) 這個結論是比較顯然的,時間複雜度是預處理的 \(o(n\log_2 n+m \log_2 n)\) 。
那麼再引入乙個問題:
在一棵樹上,進行m次操作,每一次操作使兩個節點之間的節點(包括兩個節點)的權值全部加 k ,最後求某兩個節點之間的距離
這個問題也比較簡單,就是用樹上差分跑一遍,然後再掃一遍這個鏈就可以了,時間複雜度是 \(o(n)\) 的,非常的迅猛。
但是如果將這兩個問題融合在一起,那麼整個思路就變得不好想了,要不就是 \(o(\log_2 n)\) 查詢,\(o(n)\) 修改,要不就是 \(o(1)\) 修改,\(o(n)\) 查詢,兩個時間複雜度都會爛掉,所以說引入了這樣乙個演算法:樹鏈剖分。
樹鏈剖分,顧名思義,就是將一棵樹分成若干個鏈,然後進行操作的意思,那麼怎麼樣子才能用乙個比較優秀的時間複雜度完成上面說到的兩個操作呢?
我們從淺入深講解樹鏈剖分的實現。
首先,對於乙個樹而言,如果說我們將其進行重輕鏈剖分……
等會,這個重輕鏈是啥?
定義size(x)為以x為根的子樹的節點個數。 ž令v為u的兒子節點中size值最大的節點,那麼邊(u,v)被稱為重邊,樹中重邊之外的邊被稱為輕邊。啊,感覺講的還是很清楚的,但是為了體現我寫學習筆記的意義,所以我打算再講一遍。
重輕鏈剖分,是指對於任意乙個節點而言,如果說他的某乙個兒子節點中的子節點最大,那麼這個節點就是其重兒子,其餘的都是輕兒子,將該點和其連續重兒子相連所得到的一條鏈叫做重鏈,重鏈中的每一條邊都是重邊。
這種概念的東西不是很好懂,於是我畫了……於是我沒有畫圖,大家感性理解一下就可以了,看看**就很容易理解了。
那麼在重輕鏈分好之後我們根據重輕鏈的編號來進行線段樹上的操作就可以了!但是問題來了,為什麼這樣是可以的呢?因為在重輕鏈剖分時,我們會有乙個操作,使一整條重鏈上的序號都是連續的,這個操作就是標註dfs序啦,跟tarjan是一樣的。
首先根據模板題來看一下我們**的實現部分
首先是線段樹:
struct sgiment_tree
inline void pushdown(const int o)
}void build(const int o,const int lef,const int rig)
void update(const int o,const int lef,const int rig,const int x,const int y,const int k)
pushdown(o);
int mid = lef + rig >> 1;
if(x <= mid) update(ls,lef,mid,x,y,k);
if(y > mid) update(rs,mid+1,rig,x,y,k);
pushup(o);
}ll query(const int o,const int lef,const int rig,const int x,const int y)
}sgt;
某人曾說過,以上的東西最好在5分鐘之內可以打完。
然後我們就再來看一下樹鏈剖分怎麼操作。
看一下變數定義:
fa[i]:表示該節點的父親節點。有了以上的定義差不多就可以開始做了。top[i]:表示該節點所在重鏈的最頂端。
id[i]:表示該節點的dfs序。//當然用dfn也是可以的。
siz[i]:表示該點下面的子樹的大小。
dep[i]:表示該點的深度。
son[i]:表示重兒子所在的點。
wt[i]:表示當前id點的值。
有以下兩個dfs來實現整體的預處理。
dfs1:找出每個點的深度,子樹的大小,重兒子所在的點。
dfs2:找出每一條重鏈並且進行update操作建樹。
以下是**實現:
void dfs1(const int now,const int pre)
return;
}void dfs2(const int now,const int tp)
return;
}
以上就是預處理的過程,過程的時間複雜度是很顯然的 \(o(n)\) 。所以說預處理的過程的時間複雜度基本上不用算,而且這種操作也很簡單。
看一下我們要解決的問題:
使 \(x\to y\) 之間的最短路徑加上值 \(z\) 。
求 \(x\to y\) 之間的最短路徑上的權值和。
使以 \(x\) 為根的子樹上所有的點加上 \(z\) 。
求以 \(x\) 為根的子樹上所有點的權值和。
最終結果皆模p。
用樹鏈剖分來看看吧。
inline void solve1()
if(dep[x] > dep[y]) swap(x,y);
sgt.update(1,1,n,id[x],id[y],z % p);
return ;
}int ans;
inline void solve2()
if(dep[x] > dep[y]) swap(x,y);
(ans += sgt.query(1,1,n,id[x],id[y])) %= p;
printf("%lld\n",ans);
return;
}inline void solve3()
inline void solve4()
以上就是樹鏈剖分的全部內容。 樹鏈剖分學習筆記
寫 又犯了很sb的錯誤,線段樹寫錯了。好像每次都會把r l 1寫成l r 1,然後就只有20分。寫的比較醜,壓了壓之後190行。基本上是我打過的最長的乙個模板了 然後簡單介紹一下樹剖吧。樹鏈剖分,就是把樹剖分成鏈,然後用資料結構來維護這些鏈,使得詢問 修改的複雜度達到o logn o l ogn 不...
樹鏈剖分學習筆記
樹鏈剖分 mod estc oder modestcoder modest code r如果你是重兒子,你就在重路徑上。如果你是輕兒子,暴力沿著祖先向上爬最多log nlogn logn 次就可以遇到重路徑。或者到根 而樹上操作基本就是找祖先 也許有人喜歡我的碼風 include include d...
樹鏈剖分學習筆記
前言 書上只講了重鏈剖分,菜雞也只會這一種,想看其他的是別想了。要會樹鏈剖分,首先你需要了解一些概念。我們把乙個節點的所有兒子節點中子樹節點數最大的稱為重兒子,也就是size最大的子節點。size的定義我在講換根dp時說過,因此不再贅述。對於每個節點的重兒子,我們用 son x 來記錄它,父親節點到...