【树链剖分】树链剖分讲解
2016-05-16 22:47
218 查看
是什么?
将树的所有边剖分成重边和轻边从而便于维护边权信息和进行修改,应对一系列大数据树上修改查询问题。
为什么?
如果不进行剖分代价会很大:对树上边(u,v)中的边权查询修改,必然要将边权建立成搜索树或查询树进行维护,对于所有边建立后对于(u,v)修改时只能将(u,v)中所有边进行遍历修改,最坏代价(退化成链)为n*查询修改代价。而如果把(u,v)中的边变成连续的一段区间,就会大大降低时间代价。
怎么做?
{正常的题解在这里都会下一堆定义,什么重边重孩子,我觉得不容易理解}
想把(u,v)这条边变成一段可以查找的连续区间,一定要对边进行一个重新的分类和排序,对于检索也会有要求,如果我不能把(u,v)变成一段全连续的区间,但是如果能变成几部分也是很好的。
而相对理想的就是变成log2n个区间。
有的人说树链剖分就是把树边hash到log2n个区间上。
下面我只能开始下定义了(由于本人才疏学浅,没法证明根到某一点中轻重边不大于log2n,也没法证明这个是怎么想到的)
首先记:
节点n子树节点数个数size
;
节点n深度deep
;
节点n父节点fa
;
重孩子:儿子节点所有孩子中size最大的;
轻孩子:儿子节点中除了重儿子的;
重边:连接重儿子的边;
轻边:连接轻儿子的边;
重链:重边连成的链;
轻链:轻边连成的链;
再定义:
hson
表示n的重儿子标号;
top
表示n在的重链的顶节点,不在重链上为自己标号。
id
表示剖分后的节点标号;
性质
从根节点到任意节点路径上轻重边数量不大于log2n(别问我为什么);
实现方法
其实很简单,第一遍跑一遍dfs求出刚才说的size,deep,hson;
然后再一遍dfs求出top,id;
对应id算出val[id]表示id这个点下面连的边的权值。
对val建检索树,排序树。
重点
查询的时候这么查:
对(n,v)这条边:
如果n,v在一条重边上,就直接查询(id
,id[v])这一段区间即可。
如果你,v不在一条重边上,就尽量向一条重边上靠,查询(id
,id[top
]),n再跳到fa[top
]上,继续做,知道fa[top
]与v在一条重链上,就回到了上一种情况。
这里也有一些细节,因为题中往往给的边都不是按深度给的,所以对于u,v,要注意深度关系,进行交换
好了,这样我们就成功解决了对树上修改查询边权或点的问题。
下面放上代码:
赋值val:节点与父亲的边
查询:只要不在一条重链上,(深度大的)就向父节点靠
val就用线段树平衡树维护一下就行了,这里就不附代码。
将树的所有边剖分成重边和轻边从而便于维护边权信息和进行修改,应对一系列大数据树上修改查询问题。
为什么?
如果不进行剖分代价会很大:对树上边(u,v)中的边权查询修改,必然要将边权建立成搜索树或查询树进行维护,对于所有边建立后对于(u,v)修改时只能将(u,v)中所有边进行遍历修改,最坏代价(退化成链)为n*查询修改代价。而如果把(u,v)中的边变成连续的一段区间,就会大大降低时间代价。
怎么做?
{正常的题解在这里都会下一堆定义,什么重边重孩子,我觉得不容易理解}
想把(u,v)这条边变成一段可以查找的连续区间,一定要对边进行一个重新的分类和排序,对于检索也会有要求,如果我不能把(u,v)变成一段全连续的区间,但是如果能变成几部分也是很好的。
而相对理想的就是变成log2n个区间。
有的人说树链剖分就是把树边hash到log2n个区间上。
下面我只能开始下定义了(由于本人才疏学浅,没法证明根到某一点中轻重边不大于log2n,也没法证明这个是怎么想到的)
首先记:
节点n子树节点数个数size
;
节点n深度deep
;
节点n父节点fa
;
重孩子:儿子节点所有孩子中size最大的;
轻孩子:儿子节点中除了重儿子的;
重边:连接重儿子的边;
轻边:连接轻儿子的边;
重链:重边连成的链;
轻链:轻边连成的链;
再定义:
hson
表示n的重儿子标号;
top
表示n在的重链的顶节点,不在重链上为自己标号。
id
表示剖分后的节点标号;
性质
从根节点到任意节点路径上轻重边数量不大于log2n(别问我为什么);
实现方法
其实很简单,第一遍跑一遍dfs求出刚才说的size,deep,hson;
然后再一遍dfs求出top,id;
对应id算出val[id]表示id这个点下面连的边的权值。
对val建检索树,排序树。
重点
查询的时候这么查:
对(n,v)这条边:
如果n,v在一条重边上,就直接查询(id
,id[v])这一段区间即可。
如果你,v不在一条重边上,就尽量向一条重边上靠,查询(id
,id[top
]),n再跳到fa[top
]上,继续做,知道fa[top
]与v在一条重链上,就回到了上一种情况。
这里也有一些细节,因为题中往往给的边都不是按深度给的,所以对于u,v,要注意深度关系,进行交换
好了,这样我们就成功解决了对树上修改查询边权或点的问题。
下面放上代码:
vector<int> v[MAXN]; int size[MAXN],dep[MAXN],val[MAXN],id[MAXN],hson[MAXN],top[MAXN],fa[MAXN];//定义 int edge=1,num=1; struct tree{ int x,y,z; void init(){scanf("%d%d%d",&x,&y,&z);} }e[MAXN];//起点重点权值 void dfs1(int d,int f,int u){//d:深度 f:父节点 u:当前节点 dep[u]=d; size[u]=1; hson[u]=0; fa[u]=f; for(vector<int>::iterator it=v[u].begin();it!=v[u].end();it++){ if(*it==f) continue; dfs1(d+1,u,*it); size[u]+=size[*it]; if(size[hson[u]]<size[*it]) hson[u]=*it;//找重儿子 } } void dfs2(int u,int tp){ top[u]=tp;//赋值top id[u]=num++;//赋值id if(hson[u]) dfs2(hson[u],tp);//优先重儿子 for(vector<int>::iterator it=v[u].begin();it!=v[u].end();it++){ if(*it==fa[u]||*it==hson[u]) continue; dfs2(*it,*it); } }
赋值val:节点与父亲的边
for(int i=1;i<n;i++){ if(dep[e[i].x]<dep[e[i].y]) swap(e[i].x, e[i].y); val[id[e[i].x]] = e[i].z; }
查询:只要不在一条重链上,(深度大的)就向父节点靠
int find(int u,int v){ int t1=top[u],t2=top[v]; int ans=0; while(t1!=t2){ if(dep[t1]<dep[t2]){ swap(t1,t2); swap(u,v); } ans=max(query(1,id[t1],id[u]),ans); u=fa[t1]; t1=top[u]; } if(u==v) return ans; if (dep[u] > dep[v]) swap(u, v); ans = max(query(1,id[hson[u]], id[v]), ans); return ans; }
val就用线段树平衡树维护一下就行了,这里就不附代码。
相关文章推荐
- hdu-5405
- hybz1036 树的统计Count
- poj3237 Tree
- hybz2243 染色
- 【bzoj1036】【树链剖分】【ZJOI2008】树的统计Count
- BZOJ 2243 染色 [树链剖分]
- BZOJ 4326 运输计划 transport 【NOIP 2015】【树链剖分】
- 4034: [HAOI2015]T2 (树链剖分)
- SPOJ 375. Query on a tree (基于边的树链剖分)
- 1036: [ZJOI2008]树的统计Count (基于点的树链剖分)
- Poj2831 树链剖分||次小生成树
- Poj2746-Housewife Wind 树链剖分入门(边权)
- 树链剖分(详解)
- HDU3966(树链剖分)
- 树链剖分简单入门
- poj 3237 树链剖分+线段树
- poj 3237 树链剖分
- SPOJ QTREE 1-3题解
- bzoj 1036 [ZJOI2008]树的统计Count(树链剖分入门系列)
- 【HAOI2015】【BZOJ4034】树上操作T2