树分治:
树分治是一种解决树上路径问题的一种神奇方法.
它可以把复杂度为
O(nklogn...nk+1)
问题变为最高复杂度为
O(nklogn)
的问题(当然不是随便乱搞的)
假如,一颗树长得很正常,那么它的深度大约为
logn
.
但是,如果一棵树长得很畸形(比如一条链),那深度就是n了.
如果对于每一个子树,可以随便选择根节点的话,那就不必要选择原来畸形的树根而去选择树的重心来保证深度.
* 树的重心:
* 树中一个最大子树 大小(size) 最小的点
* 树的重心的最大子树的size(mxsize)
<
<script type="math/tex" id="MathJax-Element-4"><</script>SIZE/2
* 证明:
* 设重心为x,最大子树为t.
* 假设size>SIZE/2,
* 那么如果重心为t,
mxsize为 size-1,size-1
<
<script type="math/tex" id="MathJax-Element-5"><</script>size.
* 所以x不是重心.
* 所以重心的mxsize小于SIZE/2.
所以,这样复杂度就从 O(n) ,降为 O(logn) .
树链刨分:
(由于我只会轻重路径刨分,所以只讨论轻重路径刨分)
- 可以把树分为若干重路径和轻边.
- 从点v向size最大的点u连的一条边为重边,其余为轻边.
- 全为重边构成的路径为重路径.
- 它有三个性质:
- 若 e(v,to) 为轻边,size(to) <= <script type="math/tex" id="MathJax-Element-9"><=</script>size(v)/2.
- 从根到任意点只有
logn
条轻边:
- 由于size(to) <= <script type="math/tex" id="MathJax-Element-11"><=</script>size(v)/2.
- 所以经过最多 logn 条size就会变为1.
- 所以最多 logn 条轻边.
- 从根到任意点只有
logn
条重边:
- n条重边由n-1条轻边连接.
- 从根到任意点只有 logn 条轻边.
- 所以从根到任意点只有
logn
条重边.
所以树链刨分的各种操作(不使用其他数据结构)的复杂度都为 logn .
树链刨分的用途:
1.LCA:
- 每个点记录 fa[x] 和 top[x] (链的端点).
- 若top[x]=top[y],说明x,y在同一条链上.LCA为深度较小的一个点.
- 否则,使 fa[top[x]]深度大的点跳到fa[top[x]]
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <algorithm>
#define debug(x) cout<<#x<<" : "<<x<<endl
using namespace std;
const int M=100005;
template <class T> void Rd(T &res) {
res=0;
char c;
while (c=getchar(),!isdigit(c));
do res=(res<<1)+(res<<3)+(c^48);
while (c=getchar(),isdigit(c));
}
struct node {
int to;
node *nxt;
};
struct graph {
node *st[M];
void add(int x,int y) {
node *now=new node;
now->to=y;
now->nxt=st[x];
st[x]=now;
}
int sz[M],fa[M],top[M],dep[M];
void dfs(int x,int f,int d) {
sz[x]=1,fa[x]=f,dep[x]=d;
for (node *i=st[x];i;i=i->nxt) {
int to=i->to;
if (to==f) continue;
dfs(to,x,d+1);
sz[x]+=sz[to];
}
}
void rdfs(int x,int f,int tp) {
top[x]=tp;
int id=0;
for (node *i=st[x];i;i=i->nxt) {
int to=i->to;
if (to==f) continue;
if (sz[to]>sz[id]) id=to;
}
for (node *i=st[x];i;i=i->nxt) {
int to=i->to;
if (to==f) continue;
if (to==id) rdfs(to,x,tp);
else rdfs(to,x,to);
}
}
int LCA(int x,int y) {
if (dep[x]>dep[y]) swap(x,y);
if (top[x]==top[y]) return x;
int nxtx=fa[top[x]];
int nxty=fa[top[y]];
if (dep[nxtx]>dep[nxty]) return LCA(nxtx,y);
else return LCA(x,nxty);
}
} g;
int main() {
int n,m;
Rd(n),Rd(m);
for (int i=1,x,y;i<n;++i) {
Rd(x),Rd(y);
g.add(x,y);
g.add(y,x);
}
g.dfs(1,1,1);
g.rdfs(1,1,1);
for (int i=1,x,y;i<=m;++i) {
Rd(x),Rd(y);
printf("%d\n",g.LCA(x,y));
}
return 0;
}
2.关于树形态不变,而边权点权改变的问题.
- 在dfs刨分时,记录id,让重链的编号连续.
- 用线段树维护编号对应权值.
struct seg_tree {
int add[M<<2];
void clear() {
memset(add,0,sizeof(add));
}
void down(int p) {
add[p<<1]+=add[p];
add[p<<1|1]+=add[p];
add[p]=0;
}
void update(int x,int st,int ed,int L=1,int R=M-1,int p=1) {
if (st==L&&ed==R) {
add[p]+=x;
return;
}
int mid=(L+R)>>1;
if (ed<=mid) update(x,st,ed,L,mid,p<<1);
else if (st>mid) update(x,st,ed,mid+1,R,p<<1|1);
else update(x,st,mid,L,mid,p<<1),update(x,mid+1,ed,mid+1,R,p<<1|1);
}
int query(int x,int L=1,int R=M-1,int p=1) {
if (L==R) return add[p];
down(p);
int mid=(L+R)>>1;
if (x<=mid) return query(x,L,mid,p<<1);
else return query(x,mid+1,R,p<<1|1);
}
};
struct node {
int to;
node *nxt;
};
struct graph {
node *st[M];
seg_tree tree;
void add(int x,int y) {
node *now=new node;
now->to=y,now->nxt=st[x],st[x]=now;
}
int sz[M],fa[M],top[M],dep[M],ID[M];
void dfs(int x,int f,int d) {
sz[x]=1,fa[x]=f,dep[x]=d;
for (node *i=st[x];i;i=i->nxt) {
if (i->to==f) continue;
dfs(i->to,x,d+1);
sz[x]+=sz[i->to];
}
}
int tot;
void rdfs(int x,int f,int tp) {
ID[x]=++tot;
top[x]=tp;
int id=0;
for (node *i=st[x];i;i=i->nxt) {
if (i->to==f) continue;
if (sz[i->to]>sz[id]) id=i->to;
}
if (id) rdfs(id,x,tp);
for (node *i=st[x];i;i=i->nxt) {
if (i->to==f||i->to==id) continue;
rdfs(i->to,x,i->to);
}
}
void Init() {
tot=0;
dfs(1,1,1),rdfs(1,1,1);
}
void ADD(int k,int x,int y) {
if (dep[x]>dep[y]) swap(x,y);
if (top[x]==top[y]) {
tree.update(k,ID[x],ID[y]);
return;
}
int nxtx=top[x],nxty=top[y];
if (dep[nxtx]>dep[nxty]) {
tree.update(k,ID[nxtx],ID[x]);
return ADD(k,fa[nxtx],y);
}
else {
tree.update(k,ID[nxty],ID[y]);
return ADD(k,x,fa[nxty]);
}
}
int QUE(int x) {
return tree.query(ID[x]);
}
void clear() {
tree.clear();
for (int i=0;i<M;++i) {
for (node *j=st[i];j;) {
node *nxt=j->nxt;
delete j;
j=nxt;
}
st[i]=NULL;
}
}
} g;
int val[M];
void solve(int n,int m,int p) {
g.clear();
for (int i=1;i<=n;++i) {
Rd(val[i]);
}
for (int i=1,x,y;i<=m;++i) {
Rd(x),Rd(y);
g.add(x,y),g.add(y,x);
}
g.Init();
for (int i=1,a,b,c;i<=p;++i) {
char s[10];
scanf("%s",s);
if (s[0]=='I'||s[0]=='D') {
Rd(a),Rd(b),Rd(c);
if (s[0]=='I') g.ADD(c,a,b);
else g.ADD(-c,a,b);
} else {
Rd(a);
printf("%d\n",g.QUE(a)+val[a]);
}
}
}
(上面是HDU3966的代码)
3. …………..