树链剖分


树链剖分常用轻重边剖分,用两次dfs实现(有时为了防止深度过大栈溢出,用bfs)

剖分过程要计算以下七个值:

1.father[x]:x在树中的父亲;

2.dep[x]:x在树中的深度;

3.size[x]:以x为根的子树大小;

4.son[x]:x的重儿子;

5.top[x]:x所在重路径的顶部节点;

6.seg[x]:x在线段树中的位置(下标);

7.rev[x]:线段树中第x个位置对应的树中节点编号,即rev[seg[x]]=x;

第一遍dfs可以计算前四个值,剩下的由第二遍dfs计算;

void dfs1(int u,int f){
	int e,v;
	size[u]=1;
	father[u]=f;
	dep[u]=dep[f]+1;
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v!=f){
			dfs1(v,u);
			size[u]+=size[v];
			if(size[v]>size[son[u]]) son[u]=v;
		}
	}
}

void dfs2(int u,int f){
	int e,v;
	if(son[u]){
		seg[son[u]]=++seg[0];
		top[son[u]]=top[u];
		rev[seg[0]]=son[u];
		dfs2(son[u],u);
	}
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(!top[v]){
			seg[v]=++seg[0];
			top[v]=v;
			rev[seg[0]]=v;
			dfs2(v,u);
		}
	}
}

构建线段树:

void build(int k,int l,int r){
	//k:节点编号;l,r:左右区间 
	int mid=l+r>>1; 
	if(l==r){//找到叶子节点 
		Max[k]=sum[k]=num[rev[l]];
		return;
	}
	build(k<<1,l,mid);
	build((k<<1)+1,mid+1,r);
	sum[k]=sum[k<<1]+sum[(k<<1)+1];
	Max[k]=max(Max[k<<1],Max[(k<<1)+1]);
}//节点维护两个信息:该区间总合,最值   

查询(u,v)路径信息:

void ask(int x,int y){
	int fx=top[x],fy=top[y];
	while(fx!=fy){
		if(dep[fx]dep[y]) swap(x,y);
	query(1,1,seg[0],seg[x],seg[y]);//已经在一条重路径上 
}