树链剖分,是个很神奇蛇皮的算法,他巧妙的运用了与分块类似的思想,来加速整块代码。不过,对于某些毒瘤题来说,树链剖分很可能会爆栈,如:一本通:染色。不过洛谷还好,不会爆栈。。。
那么这个时候,我们就需要手动模拟来实现非递归版本的树链剖分了。
注意到,整块树链剖分的代码中使用了递归的地方:
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));//最后加入重儿子节点 } }
到此,我们就成功实现了非递归的树链剖分了,撒花✿✿ヽ(°▽°)ノ✿✿