樹鏈割分即將樹分割成爲一段一段的線段區間,具體分割的過程就是一個dfs的過程,然後就可以將樹結構劃分爲數組結構,從而實現樹上的區間操作,即用線段樹或者樹狀數組處理。
首先就是一些必須知道的概念:
- 重結點:子樹結點數目最多的結點;
- 輕節點:父親節點中除了重結點以外的結點;
- 重邊:父親結點和重結點連成的邊;
- 輕邊:父親節點和輕節點連成的邊;
- 重鏈:由多條重邊連接而成的路徑;
- 輕鏈:由多條輕邊連接而成的路徑;
fa[u]:保存結點u的父親節點
dep[u]:保存結點u的深度值
siz[u]:保存以u爲根的子樹節點個數
son[u]:保存重兒子
rnk[u]:保存當前dfs標號在樹中所對應的節點
top[u]:保存當前節點所在鏈的頂端節點
tid[u]:保存樹中每個節點剖分以後的新編號(DFS的執行順序)
樹鏈剖分的兩個性質:
1,如果(u, v)是一條輕邊,那麼size(v) < size(u)/2;
2,從根結點到任意結點的路所經過的輕重鏈的個數必定都小於logn;
可以證明,樹鏈剖分的時間複雜度爲O(nlog^2n)
點權的樹鏈剖分
兩次dfs用於獲取帶最長鏈的樹鏈:
vector<int> tr[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree編號轉dfs編號
int rnk[maxn]; //dfs編號轉tree編號
int dfsnum;
void dfs1(int u,int father,int depth) //當前節點、父節點、層次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //這個點本身size=1
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i];
if(to==father)
continue;
dfs1(to,u,depth+1); //層次深度+1
sz[u]+=sz[to]; //子節點的size已被處理,用它來更新父節點的size
if(sz[to]>sz[son[u]])
son[u]=to; //選取size最大的作爲重兒子
}
}
void dfs2(int u,int t) //當前節點、重鏈頂端
{
top[u]=t;
tid[u]=++dfsnum; //標記dfs序
rnk[dfsnum]=u; //序號cnt對應節點u
if(!son[u])
return;
dfs2(son[u],t);
/*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i];
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一個點位於輕鏈底端,那麼它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
由於對於點的存儲vector只需要存儲int,因此對於邊的優化影響不大
邊存儲優化(完整的最優化樹鏈剖分 點權)
struct edge
{
int to;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree編號轉dfs編號
int rnk[maxn]; //dfs編號轉tree編號
int dfsnum;
void dfs1(int u,int father,int depth) //當前節點、父節點、層次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //這個點本身size=1
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to==father)
continue;
dfs1(to,u,depth+1); //層次深度+1
sz[u]+=sz[to]; //子節點的size已被處理,用它來更新父節點的size
if(sz[to]>sz[son[u]])
son[u]=to; //選取size最大的作爲重兒子
}
}
void dfs2(int u,int t) //當前節點、重鏈頂端
{
top[u]=t;
tid[u]=++dfsnum; //標記dfs序
rnk[dfsnum]=u; //序號cnt對應節點u
if(!son[u])
return;
dfs2(son[u],t);
/*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一個點位於輕鏈底端,那麼它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
如果不用需要最長鏈,可以修改成一次dfs(該方法會加長後續操作時間)
vector<int> tree[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int son[maxn];
int tid[maxn]; //tree編號轉dfs編號
int rnk[maxn]; //dfs編號轉tree編號
int dfsnum=0;
void init()
{
dfsnum=0;
memset(son,-1,sizeof(son));
}
//以上數組,並不是全部都需要,按照題目要求,可以用上述數組化簡操作的時候才進行記錄
void dfs(int u,int t,int father,int depth)
{
//u: 當前結點 t:該鏈的top結點 father: 父親結點 depth: 深度
top[u]=t;
fa[u]=father;
dep[u]=depth;
tid[u]=++dfsnum;
rnk[tid[u]]=u;
bool first=true;
for(int i=0;i<tree[u].size();i++)
{
int to=tree[u][i];
if(to!=fa[u])
{
if(first)
{
son[u]=to;
dfs(to,t,u,depth + 1);
first=false;
}
else
dfs(to,to,u,depth + 1);
}
}
}
int main()
{
dfs(root,root,-1,1);
}
邊權的樹鏈剖分
int num[maxn];
struct edge
{
int to;
int w;
int num;
edge(){}
edge(int a,int b,int c){to=a;w=b;num=c;}
};
vector<edge> tr[maxn];
int eMapDfs[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree編號轉dfs編號
int rnk[maxn]; //dfs編號轉tree編號
int dfsnum;
void dfs1(int u,int father,int depth) //當前節點、父節點、層次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //這個點本身size=1
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i].to;
if(to==father)
continue;
num[to]=tr[u][i].w; //承認to是u的兒子了,將邊權下放
eMapDfs[tr[u][i].num]=to; //當前邊的編號對應的tree樹的編號
dfs1(to,u,depth+1); //層次深度+1
sz[u]+=sz[to]; //子節點的size已被處理,用它來更新父節點的size
if(sz[to]>sz[son[u]])
son[u]=to; //選取size最大的作爲重兒子
}
}
void dfs2(int u,int t) //當前節點、重鏈頂端
{
top[u]=t;
tid[u]=++dfsnum; //標記dfs序
rnk[dfsnum]=u; //序號cnt對應節點u
if(!son[u])
return;
dfs2(son[u],t);
/*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一個點位於輕鏈底端,那麼它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(num,0,sizeof(num));
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
邊vector的存儲優化
(完整的最優化樹鏈剖分 邊權)
struct edge
{
int id;
int to;
int w;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w,int id)
{
e[tot].to=v;
e[tot].next=head[u];
e[tot].id=id;
e[tot].w=w;
head[u]=tot;
tot++;
}
int num[maxn];
int eMapDfs[maxn]; //邊序號轉該邊對應的權值下放的樹節點編號
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn]; //用於處理樹鏈剖分的長鏈短鏈
int son[maxn];
int tid[maxn]; //tree編號轉dfs編號
int rnk[maxn]; //dfs編號轉tree編號
int dfsnum;
void dfs1(int u,int father,int depth) //當前節點、父節點、層次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //這個點本身size=1
for(int i=head[u];i!=-1;i=e[i].next)
{ //當前邊爲e[i]
int to=e[i].to;
if(to==father)
continue;
num[to]=e[i].w;
eMapDfs[e[i].id]=to;
dfs1(to,u,depth+1); //層次深度+1
sz[u]+=sz[to]; //子節點的size已被處理,用它來更新父節點的size
if(sz[to]>sz[son[u]])
son[u]=to; //選取size最大的作爲重兒子
}
}
void dfs2(int u,int t) //當前節點、重鏈頂端
{
top[u]=t;
tid[u]=++dfsnum; //標記dfs序
rnk[dfsnum]=u; //序號cnt對應節點u
if(!son[u])
return;
dfs2(son[u],t);
/*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一個點位於輕鏈底端,那麼它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(num,0,sizeof(num));
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
當樹鏈構造成功之後,我們就可以利用線段樹來維護樹上的信息,來處理某點子樹和到根路徑的信息。
通過dfs我們已經保證一條重鏈上各個節點dfs序連續,那麼可以想到,我們可以通過數據結構(以線段樹爲例)來維護一條重鏈的信息
回顧上文的那個題目,修改和查詢操作原理是類似的,以查詢操作爲例,其實就是個LCA,不過這裏使用了top來進行加速,因爲top可以直接跳轉到該重鏈的起始結點,輕鏈沒有起始結點之說,他們的top就是自己。需要注意的是,每次循環只能跳一次,並且讓結點深的那個來跳到top的位置,避免兩個一起跳從而插肩而過。
樹鏈構建線段樹:
❤注意:由於線段樹是跟隨dfs序號構建的,因此需要用tid將樹編號轉化成dfs編號(下例)❤
ll sum[maxn<<2]; //隨其他修改
void build(int l,int r,int pos) //k代表了掛該數據存儲位置在第幾個數組中,l,r代表該位置數組覆蓋範圍
{
if(l==r)
{
sum[pos]=num[rnk[l]]; //dfs編號轉換成tree編號,在轉換成樹上val值
return;
}
int mid=(l+r)/2;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}
當線段樹構建完成後,就可以在線段樹上進行一系列的修改與查詢過程了。具體的修改與查詢與具體的操作有關
圖與樹的dfs:https://blog.csdn.net/qq_38890926/article/details/81222698
樹鏈路徑
求取結點u到v路徑上鍊的值(值在點上)
ll getTreeLine(int u,int v) //傳入樹編號
{
ll ans=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=ans+query(1,n,1,tid[top[u]],tid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=ans+query(1,n,1,tid[u],tid[v]);
return ans;
}
求取結點u到v路徑上鍊的值(值在邊上)
ll getTreeLine(int u,int v) //傳入樹編號
{
ll ans=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=ans+query(1,n,1,tid[top[u]],tid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=ans+query(1,n,1,tid[u]+1,tid[v]);
return ans;
}
點權,單點修改區間查詢
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>
#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}
int n;
struct edge
{
int to;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int a[maxn];
struct TreeSplit
{
int tp[maxn],fa[maxn],d[maxn],son[maxn];//關心鏈上信息
int tid[maxn],rnk[maxn];//tr轉dfs,dfs轉tr
int dfn,sz[maxn];
void dfs1(int u,int f,int dep)
{
fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;if(to==f)continue;
dfs1(to,u,dep+1);
sz[u]=sz[u]+sz[to];
if(sz[to]>sz[son[u]])son[u]=to;//重兒子size最大
}
}
void dfs2(int u,int t)//當前點,重鏈頂
{
tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
if(!son[u])return;
dfs2(son[u],t);//先重兒子保證重鏈dfs序連續
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u]&&to!=fa[u])dfs2(to,to);//點在輕鏈底端,top是它自己
}
}
void treeSplit(int rt)
{
dfn=0;
dfs1(rt,-1,0);dfs2(rt,rt);
}
ll getTreeLinemax(int u,int v);
ll getTreeLinesum(int u,int v);
}tree;
struct node
{
int l,r;
ll sum;
ll maxnum;
};
struct SegmentTree //單點修改,區間查詢
{
node tr[maxn<<2];
inline void buildInit(int p)
{
tr[p].maxnum=tr[p].sum=a[tree.rnk[tr[p].l]];
}
inline void push_up(int p)
{
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
tr[p].maxnum=max(tr[p<<1].maxnum,tr[p<<1|1].maxnum);
}
inline void changeOp(int p,ll v)
{
tr[p].maxnum=tr[p].sum=v;
}
inline node queryUnion(node a,node b)
{
node tmp;
tmp.sum=a.sum+b.sum;
tmp.maxnum=max(a.maxnum,b.maxnum);
return tmp;
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
void change(int p,int t,ll v)
{
if(tr[p].l==tr[p].r){changeOp(p,v);return;}
int mid=(tr[p].l+tr[p].r)>>1;
if(t<=mid) change(p<<1,t,v);
else change(p<<1|1,t,v);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
ll TreeSplit::getTreeLinemax(int u,int v) //傳入樹編號
{
ll ans=-inf;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=max(ans,st.query(1,tid[tp[u]],tid[u]).maxnum);
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=max(ans,st.query(1,tid[v],tid[u]).maxnum);
return ans;
}
ll TreeSplit::getTreeLinesum(int u,int v) //傳入樹編號
{
ll ans=0;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=ans+st.query(1,tid[tp[u]],tid[u]).sum;
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=ans+st.query(1,tid[v],tid[u]).sum;
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=1;i<n;i++)
{
int u,v;scanf("%d %d",&u,&v);
addedge(u,v);addedge(v,u);
}
tree.treeSplit(1);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
st.build(1,n,1);
int m;scanf("%d",&m);
for(int i=1;i<=m;i++)
{
char op[10];int x,y;scanf("%s %d %d",op,&x,&y);
if(op[3]=='X')printf("%lld\n",tree.getTreeLinemax(x,y));
if(op[3]=='M')printf("%lld\n",tree.getTreeLinesum(x,y));
if(op[3]=='N')st.change(1,tree.tid[x],y);
}
}
return 0;
}
邊權,區間查詢
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>
#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e9+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}
int n,q;
struct edge
{
int to;
int w;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
e[tot].to=v;
e[tot].w=w;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int a[maxn];
struct TreeSplit
{
int tp[maxn],fa[maxn],d[maxn],son[maxn];//關心鏈上信息
int tid[maxn],rnk[maxn];//tr轉dfs,dfs轉tr
int dfn,sz[maxn];
void dfs1(int u,int f,int dep)
{
fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;if(to==f)continue;
a[to]=e[i].w;
dfs1(to,u,dep+1);
sz[u]=sz[u]+sz[to];
if(sz[to]>sz[son[u]])son[u]=to;//重兒子size最大
}
}
void dfs2(int u,int t)//當前點,重鏈頂
{
tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
if(!son[u])return;
dfs2(son[u],t);//先重兒子保證重鏈dfs序連續
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u]&&to!=fa[u])dfs2(to,to);//點在輕鏈底端,top是它自己
}
}
void treeSplit(int rt)
{
dfn=0;
dfs1(rt,-1,0);dfs2(rt,rt);
}
ll getTreeLinemin(int u,int v);
}tree;
struct node
{
int l,r;
ll minnum;
node(){}
node(ll x){minnum=x;}
};
struct SegmentTree //單點修改,區間查詢
{
node tr[maxn<<2];
inline void buildInit(int p)
{
tr[p].minnum=a[tree.rnk[tr[p].l]];
}
inline void push_up(int p)
{
tr[p].minnum=min(tr[p<<1].minnum,tr[p<<1|1].minnum);
}
inline node queryUnion(node a,node b)
{
node tmp;
tmp.minnum=min(a.minnum,b.minnum);
return tmp;
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(lt>rt)return node(inf);
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
ll TreeSplit::getTreeLinemin(int u,int v) //傳入樹編號
{
ll ans=inf;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=min(ans,st.query(1,tid[tp[u]],tid[u]).minnum);
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=min(ans,st.query(1,tid[v]+1,tid[u]).minnum);
return ans;
}
int main()
{
cin>>n>>q;
init();
for(int i=1;i<n;i++)
{
int f,t,w;cin>>f>>t>>w;
addedge(f,t,w);addedge(t,f,w);
}
memset(a,0,sizeof(a));
tree.treeSplit(1);
st.build(1,n,1);
while(q--)
{
int f,t;cin>>f>>t;
cout<<tree.getTreeLinemin(f,t)<<endl;
}
return 0;
}