树链剖分

树剖是个神奇的东西~

其实也没有那么神奇~

首先要知道树剖是什么:将一颗树分成若干条链后,对每一个链用数据结构进行维护。

我们最常用的就是开一颗线段树保存所有树链(显然我们要保证有序)

如何分链?dalao们称它叫启发式合并,什么意思呢?

对于一颗以v为根的子树,我们选择它若干儿子中,儿子的儿子数(包括儿子自己)最多的那一个儿子与v相连直到叶子节点,这么一条路径我们称它为重路径,路径上的边我们成为重边,其余的则称轻边。

#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
int n,m,cnt;
struct Graph{//构图finish 
	vector<int>G[N];
	int p[N],fa[N];
	int siz[N],son[N],dep[N];
	int top[N];
	#define to G[x][i]
	inline void dfs2(int x,int sp){
		top[x]=sp;//我们记录每一条链的链顶,对于每个轻儿子,他的top等于自己 
		p[x]=++cnt;//线段树上的映射 
		if(son[x])dfs2(son[x],sp);
		int sz=G[x].size()-1;
		FOR(i,0,sz)if(to^fa[x]&&to^son[x])dfs2(to,to);
	}
	inline void dfs1(int x,int Fa,int depth){
		int sz=G[x].size()-1;
		FOR(i,0,sz)if(to^Fa){
			dfs1(to,fa[to]=x,dep[to]=depth+1);
			siz[x]+=siz[to];//更新size值 
			if(siz[son[x]]<siz[to])son[x]=to;//求重儿子 
		}
	}
	inline void init(){
		scanf("%d",&n);
		FOR(i,1,n-1){
			int x,y;scanf("%d%d",&x,&y);
			G[x].push_back(y);
			G[y].push_back(x);
		}FOR(i,1,n)siz[i]=1;//初始化每个siz 
	}
}g;

我们简称v的轻儿子为ls,重儿子为ws

由轻重儿子的性质易知,size(ls)<size(v)/2,否则它就是ws

对于我们树剖的操作过程,在此我们我们以树上求和为例(修改和查询一模一样,只是换函数名而已):

#define top(a) g.top[a]
#define dep(a) g.dep[a]
#define p(a) g.p[a]
inline void GETSUM(int x,int y){
	int t1=top(x),t2=top(y);
	int ans=0;
	while(t1!=t2){
		if(dep(t1)<dep(t2))swap(x,y),swap(t1,t2);//我们只针对深度深的进行操作 
		ans+=t.Sum(p(t1),p(x));
		x=g.fa[t1];//显然这一块链都是被包含在内的,所以我们要跨越此链 
		t1=top(x);//更新top 
	}if(dep(x)>dep(y))swap(x,y);//当在一条链上时,我们就只用操作现在的x~y区间 
	cout<<ans+t.Sum(p(x),p(y))<<"\n";
}

显然我们的操作最多向上爬2log2(n)次,因为轻重儿子的性质决定了向上爬的次数,我们每一次修改是log2(n)的,因此树剖的时间复杂度大概是log2(n)^2,还算优秀

只要会了线段树,树剖就ok,因此线段树才是基础。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值