实现非递归树链剖分

​ 树链剖分,是个很神奇蛇皮的算法,他巧妙的运用了与分块类似的思想,来加速整块代码。不过,对于某些毒瘤题来说,树链剖分很可能会爆栈,如:一本通:染色不过洛谷还好,不会爆栈。。。

​ 那么这个时候,我们就需要手动模拟来实现非递归版本的树链剖分了。

​ 注意到,整块树链剖分的代码中使用了递归的地方:

1.两次dfs求dfs序

2.线段树

​ 而其中线段树,我们完全不用改。因为,它的运行层数才log(n)层(要改的话可以去学下zkw线段树)

​ 那么,我们着重看两次dfs。

​ 第一次dfs:

​ 这次dfs,需要我们求出所有节点的:

​ ​1.父亲

2.层数

3.子树大小(包括自身)

4.重儿子

​ 首先是层数和父亲,这个咱们普通地扫一遍bfs就ok了,不用多说。

​ 然后,我们可以用vector倒扫法或者topo序法两种方法倒扫一遍整个树,就像做树型dp样,再搞一遍bfs,就可以求出3和4了,这里不做过多阐述,直接放一波代码(我是用的几乎没人用的vector倒扫法(其实是我某场考试yy出来的产物)):

queue<int>s;
s.push(x);
dep[x]=1;
ceng[1].push_back(x);//每层save 
while(!s.empty()){//第一遍bfs 
	int x=s.front();
	s.pop();
	for(int i=las[x];i;i=t[i].nex){
		int v=t[i].v;
		if(!dep[v]){
			dep[v]=dep[x]+1,fa[v]=x;
			ceng[dep[v]].push_back(v);//每层save 
			maxe=dep[v];//记录最大层数 
			s.push(v);
		}
	}
}
int now=maxe;//倒扫 
while(now){
	int len=ceng[now].size();
	for(int i=0;i<len;++i){
		int u=ceng[now][i];//当前节点 
		siz[u]=1;
		int maxt=0;
		for(int j=las[u];j;j=t[j].nex){
			int v=t[j].v;
			if(dep[v]>dep[u]){
				siz[u]+=siz[v];//求子树大小 
				if(siz[v]>maxt){//求重儿子 
					son[u]=v;
					maxt=siz[v];
				}
			}
		}
	}
	now--;
}

第二次dfs:

​ 本次dfs是要求每个节点的:

1.链顶

2.dfs序

3.相应dfs序所对应的颜色

​ 那么,求这个,就需要我们用"栈"这个神奇的sb的玩意儿来模拟dfs

​ 我们利用栈先进后出的性质,优先拿出一个点的重儿子,然后再利用重儿子更新重孙子(滑稽)。。。

​ 这样我们就可以按dfs的思路更新完一条树链了。

​ 然后依次类推。。。

​ 那么怎么优先更新重儿子呢?栈啊,他是先进后出,于是,我们就让它最后加入就好了啊。。。

​ 代码(我这里用的stl,相信大家都看得懂,还有,我用的pair,来模拟dfs传入的两个值):

stack<pair<int,int> >s;//定义pair栈 
s.push(make_pair(1,1));
while(!s.empty()){
	int x=s.top().first,y=s.top().second;//取出栈顶 
	s.pop();//弹出栈顶 
	top[x]=y,id[x]=++cnt,col[cnt]=W[x];//各种赋值 
	for(int i=las[x];i;i=t[i].nex){
		int v=t[i].v;
		if(v!=fa[x]&&v!=son[x]){
			s.push(make_pair(v,v));
		}
	}
	if(son[x]){
		s.push(make_pair(son[x],y));//最后加入重儿子节点 
	}
}

​ 到此,我们就成功实现了非递归的树链剖分了,撒花✿✿ヽ(°▽°)ノ✿✿

原文地址:https://www.cnblogs.com/ThinkofBlank/p/10297253.html