树链剖分正确的入门姿势
2017-07-31 17:10
543 查看
树链剖分并不是一个复杂的算法或者数据结构,只是能把一棵树拆成链来处理而已,换一种说法,树链剖分只是xxx数据结构/算法在树上的推广,或者说,树链剖分只是把树hash到了几段连续的区间上。(引用kuangbin大佬的)
个人对树链剖分的理解:
因为线段树只能处理像数组一样的线性的数据,对于树这样的结构,它显得很无力,所以我们就用“树链剖分”这种东西将树拆成一小段一小段连续的链,这样我们就得到了线性的数据,但这个线性的数据使用是有限制的,比如你跨段查询或修改的时候就会出错,那怎么办呢?我们知道此时任意两个节点之间的路径都被分成了若干段,既然不能跨段,那我们就一段一段处理,然后再合并一下。这时你可能会有疑问:(没有的话,假设你有0.0)这种分段做法和暴力有多大区别?这就要看你是怎样分段的了!我们要尽量使每段更长些,那么我们就可以将任务尽可能多的交给线段树处理,(具体处理看代码),这样其实复杂度差不多是log级别的!
大致操作过程:
通过两次dfs将整颗树分成轻链和重链(牢记同一重链在线段树上是连续的),这样做是为了将树拆成一段一段连续的小链然后放到线段上去,当查询两个节点之间的东西时,通过top[]数组和fa[]数组将这两个节点向上挪动,每次将dis[]较深的b移到fa[top[b]]处(此过程中顺便处理top[b]->b在线段树上值),重复此过程直到两个节点的top[]相同,top[]相同就意味着此时这两个节点在同一重链上,此时再重复一遍(同一重链在线段树上是连续的),所以省下可以直接就用线段树处理这两个节点之间的值了。
技巧:
处理以节点u为根的整个子树:用线段树处理[ wei[u] , wei[u] + siz[u] - 1]即可
查询节点u和节点v的LCA(最近公共祖先):将两点不断向上挪动并处理,直到两点的top[]相同,dis[]小的点即是最近公共祖先
处理节点u到节点v的路径上的点:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[wei[u] , wei[v] ]即可
处理节点u到节点v的路径上的边:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[wei[ son[u] ] , wei[v] ]即可(用每个点表示通向其父节点的边)
入门:树链剖分入门详解 (讲的很详细,不懂得细心看哦)
训练题:树链剖分从入门到精通 (难度不一,适合从入门到深入哦)
下面贴个入门参照题(参照此代码,A了入门题,就差不多算入门了)
Good Luck!(将代码注释全部打开即是POJ 2337的代码)
个人对树链剖分的理解:
因为线段树只能处理像数组一样的线性的数据,对于树这样的结构,它显得很无力,所以我们就用“树链剖分”这种东西将树拆成一小段一小段连续的链,这样我们就得到了线性的数据,但这个线性的数据使用是有限制的,比如你跨段查询或修改的时候就会出错,那怎么办呢?我们知道此时任意两个节点之间的路径都被分成了若干段,既然不能跨段,那我们就一段一段处理,然后再合并一下。这时你可能会有疑问:(没有的话,假设你有0.0)这种分段做法和暴力有多大区别?这就要看你是怎样分段的了!我们要尽量使每段更长些,那么我们就可以将任务尽可能多的交给线段树处理,(具体处理看代码),这样其实复杂度差不多是log级别的!
大致操作过程:
通过两次dfs将整颗树分成轻链和重链(牢记同一重链在线段树上是连续的),这样做是为了将树拆成一段一段连续的小链然后放到线段上去,当查询两个节点之间的东西时,通过top[]数组和fa[]数组将这两个节点向上挪动,每次将dis[]较深的b移到fa[top[b]]处(此过程中顺便处理top[b]->b在线段树上值),重复此过程直到两个节点的top[]相同,top[]相同就意味着此时这两个节点在同一重链上,此时再重复一遍(同一重链在线段树上是连续的),所以省下可以直接就用线段树处理这两个节点之间的值了。
技巧:
处理以节点u为根的整个子树:用线段树处理[ wei[u] , wei[u] + siz[u] - 1]即可
查询节点u和节点v的LCA(最近公共祖先):将两点不断向上挪动并处理,直到两点的top[]相同,dis[]小的点即是最近公共祖先
处理节点u到节点v的路径上的点:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[wei[u] , wei[v] ]即可
处理节点u到节点v的路径上的边:将两点不断向上挪动并处理,直到两点的top[]相同,最后用线段树处理[wei[ son[u] ] , wei[v] ]即可(用每个点表示通向其父节点的边)
入门:树链剖分入门详解 (讲的很详细,不懂得细心看哦)
训练题:树链剖分从入门到精通 (难度不一,适合从入门到深入哦)
下面贴个入门参照题(参照此代码,A了入门题,就差不多算入门了)
Good Luck!(将代码注释全部打开即是POJ 2337的代码)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std; struct Edge { int a,b,len; }edge[10005]; int tree[40005]; int son[10005],fa[10005],dis[10005],top[10005],siz[10005],wei[10005],len[10005],num; vector<int>vec[10005]; void dfs1(int u,int f,int d)///树链剖分套路 { fa[u]=f; dis[u]=d; siz[u]=1; son[u]=0; for(int i=0;i<vec[u].size();i++){ int v=vec[u][i]; if(v==f)continue; dfs1(v,u,d+1); siz[u]+=siz[v]; if(siz[son[u]]<siz[v])son[u]=v; } } void dfs2(int u,int tp)///树链剖分套路 { top[u]=tp; wei[u]=++num; if(son[u])dfs2(son[u],tp); for(int i=0;i<vec[u].size();i++){ int v=vec[u][i]; if(v==fa[u]||v==son[u])continue; dfs2(v,v); } } void up(int node)///线段树更新 { int ll=node<<1,rr=ll|1; tree[node]=max(tree[ll],tree[rr]); } void maketree(int l,int r,int node)///建线段树 { if(l==r){tree[node]=len[l];return;} int mid=(l+r)>>1; maketree(l,mid,node<<1); maketree(mid+1,r,node<<1|1); up(node); } void gai(int l,int r,int node,int sign,int a)///线段树修改 { if(l>sign||r<sign)return; if(l==r){tree[node]=a;return;} int mid=(l+r)>>1; gai(l,mid,node<<1,sign,a); gai(mid+1,r,node<<1|1,sign,a); up(node); } int query(int l,int r,int node,int ll,int rr)///线段树查询 { if(l>rr||r<ll)return -999999999; if(l>=ll aede &&r<=rr)return tree[node]; int mid=(l+r)>>1,ans; ans=query(l,mid,node<<1,ll,rr); ans=max(ans,query(mid+1,r,node<<1|1,ll,rr)); return ans; } /*void gai2(int l,int r,int node,int ll,int rr) { if(l>rr||r<ll)return; if(l==r){tree[node]*=-1;return;} int mid=(l+r)>>1; gai2(l,mid,node<<1,ll,rr); gai2(mid+1,r,node<<1|1,ll,rr); up(node); }*/ int yongth(int a,int b)///树链剖分路径分段处理 { if(a==b)return 0; int ta=top[a],tb=top[b],ans=-999999999; while(ta!=tb){ if(dis[ta]>dis[tb]){ swap(ta,tb); swap(a,b); } ans=max(ans,query(1,num,1,wei[tb],wei[b])); b=fa[tb]; tb=top[b]; } if(a==b)return ans; if(dis[a]>dis[b])swap(a,b); ans=max(ans,query(1,num,1,wei[son[a]],wei[b])); return ans; } /*void Yongth(int a,int b) { int ta=top[a],tb=top[b]; while(ta!=tb){ if(dis[ta]>dis[tb]){ swap(ta,tb); swap(a,b); } gai2(1,num,1,wei[tb],wei[b]); b=fa[tb]; tb=top[b]; } if(a==b)return; if(dis[a]>dis[b])swap(a,b); gai2(1,num,1,wei[son[a]],wei[b]); }*/ void init(int n)///初始化 { for(int i=0;i<=n;i++){ vec[i].clear(); } } int main() { int T; scanf("%d",&T); while(T--){ int n; scanf(" %d",&n); init(n); int a,b; for(int i=1;i<n;i++){///此处待优化,待读者深入时自会知晓,迫不及待的可以去看我的另一篇树链剖分题解 scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].len); vec[edge[i].a].push_back(edge[i].b); vec[edge[i].b].push_back(edge[i].a); } dfs1(1,1,1); num=0; dfs2(1,1); for(int i=1;i<n;i++){///将每条边的权值按wei[]的顺序记录在len[]里面,len[]即是线段树将要处理的数组 if(dis[edge[i].a]<dis[edge[i].b])swap(edge[i].a,edge[i].b); len[wei[edge[i].a]]=edge[i].len; } maketree(1,num,1); char ch[30]; while(~scanf("%s",ch)&&ch[0]!='D'){ scanf(" %d%d",&a,&b); if(ch[0]=='Q')printf("%d\n",yongth(a,b)); ///else if(ch[0]=='N')Yongth(a,b); else gai(1,num,1,wei[edge[a].a],b); } } return 0; }
相关文章推荐
- Python爬虫:最正确的入门姿势
- 这才是简单快速入门Python的正确姿势!
- Android开发入门的正确姿势,你get到了吗?
- RN 入门之-组件创建的最新正确姿势(⊙o⊙)哦
- 微信小程序入门正确姿势(一)
- 入门Promise的正确姿势
- 入门TrafficServer插件开发的正确姿势
- CDQ分治正确的入门姿势
- 正确开启Mockjs的三种姿势:入门参考(一)
- Apache 下载正确姿势
- AS 关于Freeline的正确使用姿势
- Fragment全解析系列(二):正确的使用姿势
- Angular1中数据请求$http服务的正确使用姿势
- 大数据职业规划的N种正确姿势
- AI 玩跳一跳的正确姿势,Auto-Jump 算法详解
- 正确的科研姿势
- 高版本jquery尤其是1.10.2的版本设置input radio设置值的最正确的姿势。
- 吐槽陈年的正确姿势应该是这样的
- Android so库正确放置姿势
- 在Linux(ubuntu server)上面安装NodeJS的正确姿势