某自学的树链剖分

此文用来帮助那些像我一样在这方面迷茫的人

我们知道,如果给你一个数组,我们可以利用线段树在log2(n)的时间内完成区间或单点的查询与修改,但是,如果给你一棵树,要求点到点间对边权或点权的查询与修改,要怎么做呢?

很容易想到树上求最近公共祖先,然后暴力修改查询,但是那样做的复杂度会很高(log2(n)+n)左右,明显无法满足要求,那么能不能把树上的点或边放到线段树等数据结构中,利用数据结构的优点完成这一操作呢?

于是我们引入了树链剖分,,,(剖读(pou) = =

我对树链剖分的理解就是把树进行分割,把最多的点放在一起,做到把树变成链。

首先介绍几个概念:

重儿子:对于点u,她的子节点v中最大的是重儿子,最大的意思是该子树所包含的节点数最多

轻儿子:不是重儿子的儿子

重边:节点u与重儿子v的连边

轻边:节点u与轻儿子的连边

重链:重边连成的链

我们要实现她,可能会用到以下几个数组:

siz[i]:节点i为根的子树所包含的节点个数,fa[i]:i的爸爸,son[i]:i的重儿子编号,dep[i]:i的深度(根节点深度为1);

top[i]:节点i所处重链的顶端节点(如果她与父节点连边为轻边,则top[i]=i),p[i]:节点i在数据结构中所处的位置。

算法流程:先进行一遍dfs,可以求出siz,fa,son,dep这个都会把。

再进行一遍dfs,求出top,同时求出p具体做法:

如果有重儿子,top[son[x]]=top[x],p[son[x]]=++Index,同时对他dfs

如果没有,则top[v]=v,pos[v]=++Index,也对他dfs

然后,再把每个点插入到线段树中,就完成了建树。

那么怎么进行点到点的操作呢?

观察到因为重链上的点一定是在线段树中连续的,所以如果要对同一重链上两点进行查询,可以在O(log2(n))内用线段树完成。

那么如果不在同一重链呢?

然后就是玄学所在....

设我们要求u,v间的最大值,那么设uf=top[u],vf=top[v];如果(vf!=uf) 说明两点不在同一重链,那么找出uf和vf中深度大的,设记为u,uf,显然,x和y在线段树中的连续位置,求出两点间信息,令u=fa[uf],uf=top[u,重复上述操作,直到两点处于同一重链。

现在就好办了,再次找出深度大的点,然后线段树就好了。

(虽然并不会随便注意到。。。  为什么这样做的时间复杂度就会小呢?我们可以注意到(虽然并不会直接注意到。。。:任意一个点到根节点上的轻边与重链条数之和一定<=log2(n),也就是说一个点到根节点的操作至多只会进行log2(n)次log2(n)的线段树查询,复杂度上限即二者乘积。

如果只看文字无法理解,大家可以尝试画图,也可以看一下下面的代码,相信应该会很快掌握。

例题:BZOJ1036,树链剖分模板题。

题意:一棵树,支持单点修改,点到点求最大值,求和操作。

运用上述树链剖分知识,直接上代码(为什么我的代码总是那么长啊QAQ

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long ll;
const int maxn = 30010;
using namespace std;
int n,m;
int first[maxn];
struct edg
{
	int next;
	int to;
}e[maxn<<1];
ll val[maxn];
int top[maxn],fa[maxn],dep[maxn];
int son[maxn],p[maxn],siz[maxn];
int e_sum;
int cnt;
struct sg_tree
{
	ll mmax;
	ll sum;
}node[maxn<<2];
void add_edg(int x,int y)
{
	e_sum++;
	e[e_sum].next=first[x];
	first[x]=e_sum;
	e[e_sum].to=y;
}
void updata(int rt)
{
	node[rt].mmax=max(node[rt<<1].mmax,node[rt<<1|1].mmax);
	node[rt].sum=node[rt<<1].sum+node[rt<<1|1].sum;
}
void dfs1(int x,int f,int d)
{
	dep[x]=d;
	siz[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int w=e[i].to;
		if(w!=f)
		{
			dfs1(w,x,d+1);
			siz[x]+=siz[w];
			if(siz[w]>siz[son[x]])
				son[x]=w;
			fa[w]=x;
		}
	}
}
void dfs2(int x,int f,int y)
{
	top[x]=y;
	if(son[x])
	{
		p[son[x]]=++cnt;
		dfs2(son[x],x,top[x]);
	}
	for(int i=first[x];i;i=e[i].next)
	{
		int w=e[i].to;
		if(w!=f&&w!=son[x])
		{
			p[w]=++cnt;
			dfs2(w,x,w);
		}
	}
}
void insert(int l,int r,int rt,int pos,ll x)
{
	if(l==r)
	{
		node[rt].mmax=x;
		node[rt].sum=x;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) insert(lson,pos,x);
	else insert(rson,pos,x);
	updata(rt);
}
ll query_max(int l,int r,int rt,int left,int right)
{
	if(left<=l&&r<=right) return node[rt].mmax;
	int mid=(l+r)>>1;
	if(right<=mid) return query_max(lson,left,right);
	else if(left>mid) return query_max(rson,left,right);
	else return max(query_max(lson,left,mid),query_max(rson,mid+1,right));
}
ll getmax(int x,int y)
{
	int f1=top[x],f2=top[y];
	ll tmp=-0x3f3f3f3f;
	while(f1!=f2)
	{
		if(dep[f1]<dep[f2]) swap(f1,f2),swap(x,y);
		tmp=max(tmp,query_max(1,n,1,p[f1],p[x]));
		x=fa[f1],f1=top[x];
	}
	if(dep[x]<dep[y]) swap(x,y);
	return max(tmp,query_max(1,n,1,p[y],p[x]));
}
ll query_sum(int l,int r,int rt,int left,int right)
{
	if(left<=l&&r<=right) return node[rt].sum;
	int mid=(l+r)>>1;
	if(right<=mid) return query_sum(lson,left,right);
	else if(left>mid) return query_sum(rson,left,right);
	else return query_sum(lson,left,mid)+query_sum(rson,mid+1,right);
}
ll getsum(int x,int y)
{
	int f1=top[x],f2=top[y];
	ll tmp=0;
	while(f1!=f2)
	{
		if(dep[f1]<dep[f2]) swap(f1,f2),swap(x,y);
		tmp+=query_sum(1,n,1,p[f1],p[x]);
		x=fa[f1],f1=top[x];
	}
	if(dep[x]<dep[y]) swap(x,y);
	return tmp+query_sum(1,n,1,p[y],p[x]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d%d",&x,&y);
		add_edg(x,y);
		add_edg(y,x);
	}
	for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
	dfs1(1,-1,1);
	p[1]=++cnt;
	dfs2(1,-1,1);
	for(int i=1;i<=n;i++)
	   insert(1,n,1,p[i],val[i]);
	scanf("%d",&m);
	while(m--)
	{
		char c[10];
		int x,y;
		scanf("%s%d%d",c,&x,&y);
		if(c[1]=='H') insert(1,n,1,p[x],y);
		else if(c[1]=='M') printf("%lld\n",getmax(x,y));
		else printf("%lld\n",getsum(x,y));
	}
	return 0;
}
一开始WA了一次,后来发现自己真是萌的出血,点权可能是负数,但是我最大值一开始是0,不WA才怪!

以后不管怎么样,求Max先赋-0x3f3f3f3f,求Min先赋0x3f3f3f3f  !!!!!

感悟:自学真的非常重要,不能一直等着人教,花一点时间学一会总会学懂,不要怕看似困难的知识。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值