树链剖分
定义:
size[u] 表示以节点u为根的子树的节点个数
我们将一个节点到它的儿子中 size 值最大的那个节点的边定义为重边,其他边定义为轻边
我们称某条路径为重路径(或重链)当且仅当它全部由重边组成性质:
我们可以证明对于每个点到根的路径上都不超过 O(logn) 条轻边和 O(logn) 条重路径组成.
这种将树上的边(路径)分为重边(重路径)和轻边的做法就称称为树链剖分
有了以上定义和树链剖分的概念之后,接下来就是树链剖分可以用来干什么的问题了
树链剖分主要是用来处理树上和路径有关的问题。
将树分为若干条链的好处就是可以将问题分而治之, 根据前面提到的我们可以将任意两个点之间的路径分为为数不多的轻重链(边),很多问题是可以根据这些轻重链(边)的信息构造来得到答案的。
问题的关键是怎么对这些链(边)中的信息进行维护
这里我们需要知道一个叫dfs序的东西,dfs序相当于就是将树上的节点映射成一个线性的序列。但是这里我们和dfs序不同的一点是,在搜索的过程中我们先去访问根节点的重儿子,这样就可以将一条重路径上的点映射到一个连续的序列中了,既然是连续的序列我们就很容易想到通过线段树来对这个信息进行维护了。.
需要用到的数组和变量有:
dep[i] 表示节点 i 的深度
size[i] 表示以节点 i 为根的子树的节点数
son[i] 表示节点 i 的重儿子
fa[i] 表示节点 i 的父亲top[i] 表示节点 i 所在重链上最高的节点的编号
tid[i] 表示 i <script type="math/tex" id="MathJax-Element-48">i</script>与其父亲节点所连边在线段树中的位置题目大意
给一棵树,有两个操作:
- x c 将树上第x条边修改值为c
- u v 查询树上两个节点(u,v)之间的最短路径上权值最大的一条边的值
- 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
#define LL long long int
const int MAXN=20000;
int T;
int n;
int dep[MAXN];//保存节点的深度
int son[MAXN];//保存节点的重儿子
int fa[MAXN];//节点的父亲
int sz[MAXN];//以某个节点为根的子树中节点的个数
int top[MAXN];//top[v]表示节点v所在重链上最高的节点的编号
int tid[MAXN];//tid[v]表示v与其父亲节点所连边在线段树中的位置
int indx;//用于求dfs序(严格来说不叫dfs序,先访问重儿子的dfs序)时的标号
int maxn[MAXN<<2];//线段树
struct Edge
{
int u;
int v;
int w;
int next;
Edge(){};
Edge(int u,int v,int w,int next): u(u),v(v),w(w),next(next){}
}edge[MAXN*2];
int edgecount;
int head[MAXN];
void Init()
{
memset(head,-1,sizeof(head));
edgecount=0;
memset(dep,0,sizeof(dep));
memset(son,0,sizeof(son));
memset(fa,0,sizeof(fa));
memset(sz,0,sizeof(sz));
memset(top,0,sizeof(top));
memset(tid,0,sizeof(tid));
indx=0;
memset(maxn,0,sizeof(maxn));
}
inline void Add_edge(int u,int v,int w)
{
edge[++edgecount]=Edge(u,v,w,head[u]);
head[u]=edgecount;
edge[edgecount+n]=Edge(v,u,w,head[v]);
head[v]=edgecount+n;
}
void Dfs1(int u)//初始化dep son fa sz数组
{
dep[u]=dep[fa[u]]+1;
son[u]=0;
sz[u]=1;
for(int k=head[u];k!=-1;k=edge[k].next)
{
int v=edge[k].v;
if(v==fa[u])continue;
fa[v]=u;
Dfs1(v);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]])son[u]=v;
}
}
void Dfs2(int u,int ance)//初始化top tid数组 ance表示重链的顶端节点
{
tid[u]=++indx;
top[u]=ance;
if(son[u]!=0)Dfs2(son[u],ance);
for(int k=head[u];k!=-1;k=edge[k].next)
{
int v=edge[k].v;
if(v==fa[u])continue;
if(v!=son[u])Dfs2(v,v);
}
}
///线段树部分
inline void PushUp(int rt)
{
maxn[rt]=max(maxn[rt*2],maxn[rt*2+1]);
}
void Update(int pos,int x,int l,int r,int rt)//将pos改为x
{
if(l==r){maxn[rt]=x;return ;}
int m=(l+r)/2;
if(m>=pos)Update(pos,x,l,m,rt*2);
else Update(pos,x,m+1,r,rt*2+1);
PushUp(rt);
}
int Query(int L,int R,int l,int r,int rt)//查询区间[l,r]的最大值
{
if(L<=l && r<=R)return maxn[rt];
int m=(l+r)/2;
int ans=0;
if(m>=L)ans=max(ans,Query(L,R,l,m,rt*2));
if(m+1<=R)ans=max(ans,Query(L,R,m+1,r,rt*2+1));
return ans;
}
int Solve(int u,int v)//查询两个节点u v的最短路径上的最大权值的边
{
int ance_u=top[u];
int ance_v=top[v];
int maxm=0;
while(ance_u!=ance_v)//如果u和v不在一条重链上u v就一直往上 爬直到在一条重链上
{
if(dep[ance_u] > dep[ance_v]){swap(u,v);swap(ance_u,ance_v);}//调整成ance_u在上面
maxm=max(maxm,Query(tid[ance_v],tid[v],1,indx,1));
v=fa[ance_v];
ance_v=top[v];
}
if(u==v)return maxm;
if(dep[v] < dep[u])return max(maxm,Query(tid[son[v]],tid[u],1,indx,1));//v在上面
else return max(maxm,Query(tid[son[u]],tid[v],1,indx,1));//u在上面
}
int main()
{
int a,b,c;
char op[20];
scanf("%d",&T);
while(T--)
{
Init();
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
Add_edge(a,b,c);
}
Dfs1(1);//1作为根节点开始遍历,初始化dep,son,fa,size数组
Dfs2(1,1);
for(int i=1;i<n;i++)
{
int u=edge[i].u , v=edge[i].v , w=edge[i].w;
if(dep[v] < dep[u])swap(edge[i].u,edge[i].v);//这个地方比较巧妙
Update(tid[edge[i].v],w,1,indx,1);
}
while(scanf("%s",op))
{
if(op[0]=='D')break;
if(op[0]=='Q')//查询操作
{
scanf(" %d%d",&a,&b);
printf("%d\n",Solve(a,b));
}
else //Change修改操作
{
int id,x;
scanf("%d%d",&id,&x);
Update(tid[edge[id].v],x,1,indx,1);
}
}
}
return 0;
}