前置知識:$dfs$序,線段樹
我們可以回顧兩個問題:
1.樹上從$s$到$t$的路徑,每個點權值加上$z$。
很簡單。遍歷整棵樹即可。
2.求樹上$s$到$t$的權值和。
$lca$可做。可以利用$lca$的性質$dis[s]+dis[t]-2*dis[lca]$做即可。時間複雜度$o(n\log n)$。
但是把這兩個問題結合起來呢?
每次更改權值後都要重新算一遍$dis$。那麼時間複雜度變成$n^2$的了。這時候,我們就需要樹鏈剖分來解決此類問題。
樹鏈剖分:把一棵樹劃分成若干鏈,轉化成若干序列,並用資料結構維護的演算法。
概念:
重兒子:父親節點的所有兒子中子樹結點數目最多($size$最大)的結點;
輕兒子:父親節點中除了重兒子以外的兒子;
重邊:父親結點和重兒子連成的邊;
輕邊:父親節點和輕兒子連成的邊;
重鏈:由多條重邊連線而成的路徑;
輕鏈:由多條輕邊連線而成的路徑;
變數宣告:
int size[maxn],son[maxn],dep[maxn],fa[maxn];//大小、重兒子、深度、父親節點
int top[maxn],dfn[maxn],cnt;//
所在重鏈的頂點、時間戳
由上面的定義可知,重鏈一定是由輕邊連線的。下面我們對樹鏈剖分的複雜度進行證明。
因為每次跳樹跳的是輕邊,所以每跳一次後,所在樹的子樹的大小一定至少是原來的二倍。所以單次跳樹的複雜度是$o(n\log n)$。總複雜度$o(n\log^2 n)$。
兩次$dfs$
第一次$dfs$是進行$dfs$序,把每個結點的子樹大小,父親節點,重兒子和深度處理出來。
inline void dfs_son(int now,int f,intdeep)
}
第二次$dfs$是記錄時間戳,按照優先遍歷重兒子的原則,把樹處理成若干鏈。
inline void dfs(int now,inttopf)
}
然後進行線段樹建樹。**不貼了。
更新操作:
如果$s$和$t$不在乙個鏈內,那麼我們要進行跳樹操作。先把當前鏈的頂端到此結點的路徑權值處理了,然後跳到該鏈頂點的父親節點。類似於$lca$的操作。
inline void updrange(int x,int y,intk)
if (dep[x]>dep[y]) swap(x,y);
update(
1,dfn[x],dfn[y],k);
}
inline int qrange(int x,inty)
if (dep[x]>dep[y]) swap(x,y);
ans+=query(1
,dfn[x],dfn[y]);
ans%=mod;
return
ans;
}
如果要更新子樹,那麼就更簡單了,只有一行**:
update(1,dfn[x],dfn[x]+size[x]-1);
至此,樹鏈剖分的所有內容已講解完畢。
練習題:
1.【模板】輕重鏈剖分
2.【zjoi2008】樹的統計
3.【noi2015】軟體包管理器
4.【haoi2015】樹上操作
5.【jloi2014】松鼠的新家
都不難,稍微變通一下就是樹鏈剖分的模板題。
模板**(練習題1):
#include#define int long longusing
namespace
std;
const
int maxn=200005
;int
n,m,r,mod;
intsize[maxn],son[maxn],fa[maxn],dep[maxn],w[maxn];
inttop[maxn],wt[maxn],dfn[maxn],cnt;
int head[maxn*2
],jishu;
struct
node
edge[maxn*2
];int lazy[maxn*4
];struct
tretree[maxn*5
];inline
intread()
while(isdigit(ch))
return x*f;
}inline
void add(int
from,int
to)inline
void dfs_son(int now,int f,int
deep)
}inline
void dfs(int now,int
topf)
}inline
void build(int index,int l,int
r)
int mid=(l+r)>>1
; build(index*2
,l,mid);
build(index*2+1,mid+1
,r);
tree[index].v=(tree[index*2].v+tree[index*2+1].v)%mod;
}void pushdown(int
index)
inline
void update(int index,int l,int r,int
k)
else
}inline
int query(int index,int l,intr)}
inline
void updrange(int x,int y,int
k)
if (dep[x]>dep[y]) swap(x,y);
update(
1,dfn[x],dfn[y],k);
}inline
int qrange(int x,int
y)
if (dep[x]>dep[y]) swap(x,y);
ans+=query(1
,dfn[x],dfn[y]);
ans%=mod;
return
ans;
}inline
void updson(int x,int
k)inline
int qson(int
x)signed main()
dfs_son(r,
0,1);
dfs(r,r);
build(
1,1,n);
for (int i=1;i<=m;i++)
if (flag==2
)
if (flag==3
)
if (flag==4
)
}return0;
}
樹鏈剖分學習筆記
寫 又犯了很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 來記錄它,父親節點到...