带你从模板理解树链剖分
来源:互联网 发布:驱动精灵软件 编辑:程序博客网 时间:2024/06/07 01:14
树链剖分总结:
这几天也是学习了树链剖分,刚开始一脸懵逼,完全不知道在讲什么东西,现在终于理解了一点,感觉要想理解这个算法的话,其实结合着图题目加模板来看的话是最快的。
首先是概述(纯属个人瞎扯,扯错了请各位大佬指出,感激不尽)
树链剖分其实很像一类题目,就是将一棵树转换为线性结构,通过dfs序来遍历最终使得树上的结点成为线性的连续区间,然后利用一系列数据结构进行解题(弱鸡如我当前只会线段树),树链剖分其实就是将树上的边转换为了线性结构,但是转换的时候通过一些办法保证了时间复杂度。反正我他喵是不会证,
谈一下理解吧,个人觉得先讲明一些概念比较好,只讲比较关键的几个,首先是重儿子,u的重儿子其实就是以u为父结点的所有子结点中子树最多的那一个儿子节点,可以简单的理解为最重最复杂的那个儿子,重链呢就是把所有重儿子连接起来的链就是重链,废话不多说,上图,图是网上找的,侵删。
暂时先别管重链怎么得到的,可以看到上面的1 4 9 13 14 是明显的重链,从头连到尾,那么其他黑色边代表了其他的重链呢,其实就是对于每一个结点来说,都延伸了一个向下的重链,这样我们可以看到链上的编号是线性连续的,那么我们就可以利用数据结构来维护了。
接下来是比较关键的查询操作,查询某u,v结点间的所有边。
首先令 f1=top[u],f2=top[v];
首先判断u,v是否在一条重链上,(在此先默认是u的深度小于v,即v在u的下面),那么直接查询即可。若不在一条重链上的话,假设f1深度大于f2,那么先查询w[f1],w[u],然后令u=fa[f1],即向上爬一格。
拿上面的图举个例子,当我查询11,14的时候,我会先判断发现不在一条重链上,然后发现top[11]比top[14]大,接着查询 9 10 11号点,然后u爬到了1,接着发现1和14在同一条重链上,所以直接查询即可。
理解上面的思想后,接下来主要是通过模板和题目进行一些细节部分的讲解,模板包括两个dfs,我的模板用的是链式前向星写法,理解了这个vector写法应该不难,
以下是模板中各个数组的作用,懒得自己写了,用一下大佬的。
size[]数组,用来保存以当前节点为根的子树节点个数
top[]数组,用来保存当前节点的所在链的顶端节点
son[]数组,用来保存重儿子
dep[]数组,用来保存当前节点的深度
fa[]数组,用来保存当前节点的父亲
w[]数组,用来保存树中每个节点剖分后的新编号
以下贴代码,为spoj的Qtree,就是单纯的查询操作,也算是模板题了,通过这道题讲一下树链剖分应用部分的细节,
#include <iostream>#include <cstdio>#include <cstring>#include <string>#include <cstdlib>#include <cmath>#include <vector>#include <queue>#include <map>#include <algorithm>#include <set>#include <functional>#define pb push_back#define lson rt<<1#define rson rt<<1|1using namespace std;typedef long long ll;const int N=10010;const int INF=1e9+7;int cnt,n,step,ans;int dep[N],size[N],son[N],top[N],w[N],fa[N];int head[N];char s[20];struct node //链式前向星{ int u,v,w; int next;}edge[N<<1];struct tree{ int l,r,mid; int ma;}t[N<<2];void add(int u,int v,int w) //建边注意cnt从0开始{ edge[cnt].u=u;edge[cnt].v=v;edge[cnt].w=w; edge[cnt].next=head[u];head[u]=cnt++; return ;}void dfs1(int u,int fat,int de) //dfs1找出每个结点的重儿子,父亲结点,深度{ dep[u]=de;fa[u]=fat;size[u]=1;son[u]=0; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; //当前结点所有的子节点 if(v==fat) //这里是因为无向边的问题 continue; dfs1(v,u,de+1); size[u]+=size[v]; if(son[v]==-1||(size[son[u]]<size[v])) //找出重儿子 son[u]=v; } return ;}void dfs2(int u,int tp) //dfs2将重链连接起来{ top[u]=tp; //更新当前结点所在链的头结点 w[u]=++step; //更新u结点编号 if(son[u]>0) //优先连接重儿子形成重链 dfs2(son[u],tp); for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; //从其他儿子开始往下重新连接重链 if(v==fa[u]||v==son[u]) //排除一些不可能的选项 continue; dfs2(v,v); } return ;}void build(int l,int r,int rt) //线段树部分就不写了{ int m=(l+r)>>1; t[rt].l=l;t[rt].r=r; t[rt].mid=m; t[rt].ma=0; if(l==r) return ; build(l,m,lson); build(m+1,r,rson);}void pushup(int rt){ t[rt].ma=max(t[lson].ma,t[rson].ma);}void query(int p,int q,int rt){ if(t[rt].l>=p&&t[rt].r<=q) { ans=max(ans,t[rt].ma); return ; } if(p<=t[rt].mid) query(p,q,lson); if(q>t[rt].mid) query(p,q,rson); pushup(rt);}void update(int pos,int flag,int rt){ if(t[rt].l==t[rt].r) { t[rt].ma=flag; return ; } if(pos<=t[rt].mid) update(pos,flag,lson); if(pos>t[rt].mid) update(pos,flag,rson); pushup(rt);}void vs(int u,int v){ int f1,f2; ans=-INF; while(u!=v) //这里就是查询操作了 写法比较多 这只是其中一种 { if(dep[u]>dep[v]) //首先判断深度 swap(u,v); f1=top[u];f2=top[v]; if(f1==f2) //同一条重链上 { query(w[son[u]],w[v],1); v=u; } else if(dep[f1]>dep[f2]) //不在同一条重链上按照之前说的先查询深度大的链 { query(w[f1],w[u],1); u=fa[f1]; //结点向上爬一位 } else //同上 { query(w[f2],w[v],1); v=fa[f2]; } } if(ans==-INF) ans=0; printf("%d\n",ans); return ;}void slove(){ build(0,step,1); for(int i=0;i<n-1;i++) //注意这里的范围 因为用的链式前向星建的无向边 { int x=edge[i*2].u; //下面每个下标 *2 保证边的不重复 int y=edge[i*2].v; if(dep[x]>dep[y]) //判断深度 swap(edge[i*2].u,edge[i*2].v); update(w[edge[i*2].v],edge[i*2].w,1); //更新边的权值 } int p,q; while(scanf(" %s",s)) { if(s[0]=='D') break; scanf("%d%d",&p,&q); if(s[0]=='Q') vs(p,q); if(s[0]=='C') update(w[edge[(p-1)*2].v],q,1); //直接更新 注意*2 }}int main(){ int QAQ; scanf("%d",&QAQ); while(QAQ--) { cnt=0; memset(head,-1,sizeof head); //链式前向星初始化 scanf("%d",&n); int u,v,w; for(int i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); //建边 注意无向 add(v,u,w); } step=0; //编号初始化 dfs1(1,0,1); dfs2(1,1); slove(); }}
- 带你从模板理解树链剖分
- 带你从源码角度理解Volley实现原理
- 带你从源码的角度理解Volley
- 带你从源码的角度去理解Handler
- 带你从源码理解Looper ,handle,MessageQueue,HandleThread
- Android Volley 带你从源码的角度理解Volley
- 带你从源码的角度理解Volley
- 从源码带你理解AlertDialog.getButton为什么是null
- Android-带你从源码角度理解SharedPreferences存储原理
- 带你理解beta分布
- 带你理解beta分布
- 带你理解beta分布
- 带你简单理解Binder
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Java-UploadHelper工具类
- Mac下统计任意文件夹中代码行数的工具——cloc
- JSON详解
- 小团队Git协作流程
- Android 在线视频播放器(Vitamio)
- 带你从模板理解树链剖分
- 编译libimobiledevice方法
- Java-ConnectDB工具类
- eclipse异常报错:Unable to build: the file dx.jar was not loaded from the SDK folder
- 小程序2个页面如何接传值
- myeclipse调试debugger没出现绿色条的最有效解决方法
- 使用绘图句柄HDC在客户区、非客户区、临时客户区绘图
- ubuntu 14.04 下同时安装OpenCV2.4.8和OpenCV3.2,版本共存切换
- Android开发面试经——6.常见面试官提问Android题②