主要是根據學長的課件來透徹的。所以好多地方直接粘過來了qwq
樹鏈剖分
所謂樹鏈剖分,就是將樹上的邊進行划分。
樹鏈剖分有重鏈剖分,長鏈剖分,實鏈剖分等等。
長鏈剖分是用來\(O(1)\)求\(k\)級祖先的,和優化一些樹形DP,具體地來說是一些跟深度有關的DP。
實鏈剖分是我們常說的\(LCT(Link-Cut-Tree)\)。
本文介紹的主要是重鏈剖分。
重鏈剖分
既然是重鏈剖分,那么一定有重鏈和輕鏈,但是我們怎么來划分輕重鏈呢?
我們定義:一個節點的所有子節點中\(size\)最大的那個節點為重兒子;那么這兩個節點之間所連的邊為一條重邊。
這樣的話我們會得到一些性質。
性質1:從根節點到葉子節點的路徑上,跳重鏈的次數不會超過\(O(logn)\),從葉子節點到根節點,也成立
證明:我們考慮,什么時候會跳重鏈,一定是當他需要向輕兒子走的時候廢話。那么輕兒子的\(size\),一定小於他父親節點
的\(\frac{1}{2}\),那么不斷走輕兒子,一直乘\(\frac{1}{2}\),只需要跳\(\log n\)次,就到\(1\)了,也就是葉子節點。
首先考慮如何找到重兒子,我們知道,這個點的size就為他所有而兒子的size之和,且他兒子深度為當前點深度加1,遞歸的思路已經很明顯了qwq。
代碼:
int fa[maxn], dep[maxn], size[maxn], height_son[maxn];
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
size[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(dep[to]) continue;
dep[to] = dep[now] + 1;//下一點的深度為當前點深度加一
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重兒子
}
}
樹剖求LCA
我們考慮,如果兩個點在同一條重鏈上,那么肯定是深度小的是LCA。
但是如果不在呢?
我們是不是可以像倍增一下跳(也僅僅是像),跳重鏈,向上跳,然后一直跳到兩個點在同一條重鏈上,再像剛才一樣處理。
讓誰跳呢,深度大的?
不不不,是\(top\)深度大的跳。
因為,既然兩個點不在同一條重鏈上,那么顯然,他們的\(top\),也不在同一條重鏈上。
那么類似於倍增,讓\(top\)跳\(fa\),直到跳到同一條重鏈上。
深度小的是LCA。
如何往上跳,當兩個點所在鏈的最高點不相同時他們一定不在同一條鏈上,根據這個條件while往上跳即可(讓深度深的往上跳)
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
/*......*/
x = fa[top[x]];
}
本人還不透徹,不如去寫倍增LCA(其實倍增也不會qaq)
升華
前置芝士:線段樹。
先上一道例題來透徹一下樹鏈剖分吧。
好像不只是跳重鏈那么簡單。
在這里,我們引入一個叫做\(dfs\)序的概念。
\(dfs\)序:我們在遍歷整棵樹時,每個節點被遍歷到的時間戳(即這個節點是第幾個被遍歷到的)。
- 那么我們顯然可以發現一顆子樹內的\(dfs\)序是連續的,而且一條鏈上的\(dfs\)序也是連續的(可以畫圖理解)。
- 那么我們在詢問一條路徑時,就可以把這條路徑分成好幾條鏈,我們對於每條鏈分別統計即可。
那么我們怎么才能維護每條鏈的信息呢?
注意:一條鏈上的\(dfs\)序是連續的,那么問題轉化為,如何維護一個區間的信息?
當然是線段樹啊。
什么區間和,區間最值,區間覆蓋,線段樹簡直不能再合適了。
那么是不是這道題就做完了口牙。
那么我們還可以保證我們樹鏈剖分的時間復雜度為\(O(n\log^2n)\)了
int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn];
/*其中dfn為該點所對應的dfs序,id為該點的dfs序所對應的自己原來的編號,top為這條重鏈上最高的點*/
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
size[now] = 1;
for(int i = 0; i < v[now].size(); i ++){
int to = v[now][i];
if(dep[to]) continue;
dep[to] = dep[now] + 1;//下一點的深度為當前點深度加一
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重兒子
}
}
void dfs(int now, int topfa){ //找到點對應的dfs序
top[now] = topfa;
dfn[now] = ++cnt;
id[cnt] = now;
if(height_son[now]) dfs(height_son[now], topfa); //先走重鏈
for(int i = 0; i < v[now].size(); i ++){//走輕鏈
int to = v[now][i];
if(fa[now] == to or height_son[now] == to) continue;
dfs(to,to);
}
}
找到\(dfs\)序以后我們就把這個問題美滋滋的轉化為了區間問題。
對於\(1,2\)操作:
對於一條路徑,我們根據2.將其剖為若干條鏈,每條鏈上的\(dfs\)序是連續的,那么我們就讓深度高的點往上跳,同時記錄每一條鏈或修改每一條鏈即可,最后不要忘了記錄或修改兩點在同一條鏈上的時候,而且我們要知道,同一條鏈上的兩個點,深度高的\(dfs\)序大。
對於\(3,4\)操作:
首先我們根據1.已經知道對於一顆子樹上的\(dfs\)序是連續的,那么我們對子樹的操作轉到線段樹上對\(dfn[x]\)到\(dfn[x]+size[x]-1\)進行常規的線段樹修改和查詢操作即可。
代碼真的是又臭又難寫qaq。
總結一下我提交了20次才A掉這道題時犯的各種錯誤主要還是我太蔡徐坤了qaq:
-
如果用鏈式前向星存圖記得開雙倍空間,注意他是無向邊。
-
線段樹千萬記得開4倍空間開4倍空間4倍空間
指針請繞行。 -
用線段樹對我們剖開的樹進行維護,在構建樹時要從節點1開始,注意你構建的線段樹與題中給的樹是木有關系的,即這樣
tree.bulid_segtree(1,n,1);
-
線段樹在修改和詢問時一定要記得下放標記,並且要記得上傳。
-
每個人的問題都不一樣,還希望大家可以避免掉細節上的錯誤_(¦3」∠)_
接下來上代碼:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, m, r, mod, cnt, a[maxn];
struct segpou{
int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn], head[maxn];
struct egde{
int y, nxt;
}e[maxn*2];
inline void add(int x, int y){
e[++cnt].y = y, e[cnt].nxt = head[x];
head[x] = cnt;
return;
}
void bulid_poutree(int now){//構建樹剖所需要的信息
size[now] = 1;
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].y;
if(dep[to]) continue;
dep[to] = dep[now] + 1;
fa[to] = now;
bulid_poutree(to);
size[now] += size[to];
if(size[to] > size[height_son[now]]) height_son[now] = to;
}
}
void dfs(int now, int topfa){//找到dfs序,且先走重邊
top[now] = topfa;
dfn[now] = ++cnt;
id[cnt] = now;
if(height_son[now]) dfs(height_son[now], topfa);
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].y;
if(fa[now] == to or height_son[now] == to) continue;
dfs(to,to);
}
}
#define ls (now << 1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
struct node{
int l, r, sum, tag;
inline int get(){
return ((sum%mod)+(tag*(r-l+1)%mod))%mod;
}
}no[maxn*4];
void up(int now){
no[now].sum = ((no[ls].get()%mod) + (no[rs].get()%mod))%mod;
return;
}
void down(int now){
no[ls].tag += no[now].tag;
no[rs].tag += no[now].tag;
no[now].tag = 0;
return;
}
void bulid_segtree(int l, int r, int now){
no[now].l = l, no[now].r = r;
if(l == r) return (void)(no[now].sum = a[id[l]]);
bulid_segtree(l,mid,ls), bulid_segtree(mid+1,r,rs);
up(now);
}
void chenge(int l, int r, int now, int val){
if(r < no[now].l or no[now].r < l) return;
if(l <= no[now].l and no[now].r <= r) return(void)(no[now].tag += val);
down(now);
chenge(l,r,ls,val), chenge(l,r,rs,val);
up(now);
}
void query(int l, int r, int now, int &ans){
if(r < no[now].l or no[now].r < l) return;
if(l <= no[now].l and no[now].r <= r){
ans = (ans%mod+no[now].get()%mod)%mod;
return;
}
down(now);
query(l,r,ls,ans), query(l,r,rs,ans);
up(now);
}
int poutree_query(int x, int y){
/*對於兩個點不在同一條鏈上時,我們選擇讓更深的點往上跳,直到在同一條鏈上為止*/
int res = 0;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
int tmp = 0;
query(dfn[top[x]],dfn[x],1,tmp);
res = (res%mod+tmp%mod)%mod;
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
int tmp = 0;
query(dfn[y],dfn[x],1,tmp);
res = (res%mod+tmp%mod)%mod;
return res;
}
void poutree_chenge(int x, int y, int val){
/*修改也是如此*/
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x,y);
chenge(dfn[top[x]],dfn[x],1,val);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
chenge(dfn[y],dfn[x],1,val);
}
}tree;
signed main(){
scanf("%d%d%d%d", &n, &m, &r, &mod);
for(int i = 1; i <= n; i ++){
scanf("%d", &a[i]);
}
for(int i = 1, x, y; i < n; i ++){
scanf("%d%d", &x, &y);
tree.add(x,y); tree.add(y,x);
}
cnt = 0, tree.dep[r] = 1;
tree.bulid_poutree(r);
tree.dfs(r,r);
tree.bulid_segtree(1,n,1);
for(int cmp, x, y, z; m; m --){
scanf("%d", &cmp);
if(cmp == 1){
scanf("%d%d%d", &x, &y, &z);
tree.poutree_chenge(x,y,z);
}
if(cmp == 2){
scanf("%d%d", &x, &y);
printf("%d\n",tree.poutree_query(x,y));
}
if(cmp == 3){
scanf("%d%d", &x, &y);
tree.chenge(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,y);
}
if(cmp == 4){
scanf("%d", &x);
int ans = 0;
tree.query(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,ans);
printf("%d\n", ans);
}
}
return 0;
}
樹上差分
占坑qwq