【树剖求LCA】树剖知识点

#include<cstdio>
#include<iostream>
using namespace std;
struct edge{
    int to,ne;
}e[1000005];
int n,m,s,ecnt,head[500005],dep[500005],siz[500005],son[500005],top[500005],f[500005];
void add(int x,int y)    //加边
{
    e[++ecnt].to=y;
    e[ecnt].ne=head[x];
    head[x]=ecnt;
}
void dfs1(int x)                             //构造树
{
    siz[x]=1;                                //假设当前节点仅有一个儿子
    dep[x]=dep[f[x]]+1;                      //当前节点深度=父亲节点深度+1
    for(int i=head[x];i;i=e[i].ne)          //遍历所有的子节点
    {
        int dd=e[i].to;
        if(dd==f[x])continue;               //如果是父节点,则略过
        f[dd]=x;                             //那么确定x是当前节点的父亲
        dfs1(dd);                            //向下遍历
        siz[x]+=siz[dd];                     //遍历完子树之后,加上子树的大小
        if(!son[x]||siz[son[x]]<siz[dd])    //如果x节点重儿子未确定或者重儿子的子树比当前遍历节点的子树小
            son[x]=dd;                       //更新重儿子
    }
}

void dfs2(int x,int tv)                     //求重链
{
    top[x]=tv;                               //设置x所在重链顶为tv
    if(son[x])dfs2(son[x],tv);              //如果x有重儿子,那么随着这条重链走
    for(int i=head[x];i;i=e[i].ne)
    {
        int dd=e[i].to;
        if(dd==f[x]||dd==son[x])continue;  //如果走到父亲或者走到重儿子(已经走过重儿子,避免重复),那么跳过
        dfs2(dd,dd);                        //开启一条新链,链顶是其本身
    }
}
int lca(int x,int y)
{
	while(top[x]!=top[y])                              //如果二者不在同一条重链上
    {
		if(dep[top[x]] >= dep[top[y]]) x=f[top[x]];   //选择所在重链的顶的深度较大的点向上跳,目的是防止跳过LCA
		else y=f[top[y]];
	}
	return dep[x] < dep[y] ?x :y;                     //当二者在同一条重链上的时候,选择深度较浅的点即为lca

}
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(s);
    dfs2(s,s);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}

非优美注释版

----------------------------------------------------------

#include<iostream>
#include<vector>
#include<cstdio>
#include<queue>
#include<map>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const ll INF=99999999;
const int N = 500010;

int n,m,s;

struct edge{
	int to,ne;

}e[N*2];

int top[N],siz[N],son[N],fa[N],dep[N];
int head[N],ecnt = 1;

void add(int x,int y)
{
	e[ecnt].to = y;
	e[ecnt].ne = head[x];

	head[x] = ecnt++;
}

void dfs1(int x)
{
	siz[x] = 1;
	dep[x] = dep[fa[x]] + 1;

	for(int i = head[x];i;i = e[i].ne){
		int t = e[i].to;
		if(t == fa[x]) continue;
		fa[t] = x;

		dfs1(t);
		siz[x] += siz[t];
		if(!son[x]||siz[son[x]] < siz[t])
			son[x] = t;
	}
}

void dfs2(int x,int tp)
{
	top[x] = tp;

	if(son[x])
		dfs2(son[x],tp);

	for(int i = head[x];i;i = e[i].ne){
		int t = e[i].to;
		if(t == son[x]||t == fa[x]) continue;

		dfs2(t,t);
	}	

}

int lca(int x,int y)
{
	while(top[x] != top[y]){
		if(dep[top[x]] >= dep[top[y]]) x = fa[top[x]];
		else y = fa[top[y]];
	}
	return dep[x] < dep[y] ?x :y;
}
int main()
{

	scanf("%d%d%d",&n,&m,&s);
	for(int i = 1;i < n;i++){
		int x,y;
		scanf("%d%d",&x,&y);

		add(x,y);
		add(y,x);
	}
	dfs1(s);
	dfs2(s,s);

	for(int i = 1;i <= m;i++){
		int a,b;
		scanf("%d%d",&a,&b);

		printf("%d\n",lca(a,b));
	}
	return 0;
}

优美代码 不带注释

树剖理解容易,需要注意的是题目如果给的是双向边,e数组需要开两倍于边数

  

 

 

原文地址:https://www.cnblogs.com/dudujerry/p/10192526.html

时间: 2024-10-10 02:00:33

【树剖求LCA】树剖知识点的相关文章

树剖求LCA模板

O(logn)(n<=10^6) https://www.cnblogs.com/cangT-Tlan/p/8846408.html 把一棵树分成几条链,用数据结构去维护每一条链 1 #include<bits/stdc++.h> 2 #define ll long long 3 #define rll register ll 4 #define M 0x3f3f3f 5 #define For(i,l,r) for(int i=l;i<=r;i++) 6 using namesp

[HNOI2014][BZOJ3572] 世界树|虚树|树上倍增LCA|树型dp|dfs序

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 555  Solved: 319[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.    世界树的形态可以用一个数学模型来描述:世界树中有n个种

tarjan,树剖,倍增求lca

1.tarjan求lca Tarjan(u)//marge和find为并查集合并函数和查找函数 { for each(u,v) //访问所有u子节点v { Tarjan(v); //继续往下遍历 marge(u,v); //合并v到u上 标记v被访问过; } for each(u,e) //访问所有和u有询问关系的e { 如果e被访问过; u,e的最近公共祖先为find(e); } } 2.倍增lca(在线) #include<bits/stdc++.h> using namespace st

POJ-1330&amp;HDU-2586 最近公共祖先(不带权+带权)树剖式写法求LCA

其实敲树剖敲多了就会手敲,然后就发现其实树剖也是可以求LCA的,根据树剖的经验,我们两遍dfs后关于询问l,r的情况我们就开始跳链,当l,r处于同一个链的时候返回深度较小的那个点就好了,这里给个例题: 题目链接:http://poj.org/problem?id=1330 Description A rooted tree is a well-known data structure in computer science and engineering. An example is shown

【树链剖分】链剖相关总结与心得

这两天连着做了一些链剖也看了不少链剖已经大致明白链剖里题目特点了 链剖题目分类 我们可以把所有链剖的题目分为如下两类: 给定点权的链剖 这类链剖也是最基础的链剖,大部分题目都是这个样子的. 题目中会给定每一个点的初始点权,以此来计算路径长度. 这种题目相对来说比较简单,我们直接套模板两次DFS然后建树,把每个点在线段树上对应节点的数值modify就好了. 标准模板题可以看:ZJOI2008树的统计Count 那就是一个裸的点权链剖 给定边权的链剖 这类链剖相比上面那个会稍微复杂一点. 题目中给定

poj 1470 Closest Common Ancestors tarjan求lca和树的孩子兄弟表示

题意: 给一棵树和若干查询点对,求这些点对的lca. 分析: tarjan求lca的模板题,树还是用孩子兄弟表示法比较简洁. 代码: //poj 1470 //sepNINE #include <iostream> #include <vector> using namespace std; const int maxN=1024; int n,u,v,t,m,x,y;; int par[maxN],son[maxN],bro[maxN],f[maxN],cnt[maxN],vis

【BZOJ2588】Count on a tree,主席树维护链+ST表求LCA

传送门 写在前面:一天下来就写了两道主席树的题--(codevs上的一道智障天梯不算) 思路: 才知道原来主席树不仅可以通过dfs序维护子树区间,还可以直接维护一条到根的链-- 我们建好主席树后,每次查询u->v路径上的第k大,无非有两种情况 1.u,v在同一条链上 2.u,v不在同一条链上 其实这两种情况处理起来是一样的,我们利用主席树中的前缀和思路,root[u]并上root[v]再减去root[lca(u,v)]再减去root[fa[lca(u,v)]]就是u->v路径上的点了(可以自己

P2633|主席树+dfs序+树链剖分求lca+离散化

不知道为什么会RE.. 待补 思路:链上求u和v两点路径第k小利用lca就转变为了 U+V-LCA-FA(LCA) 上的第k小,这因为每个点的主席树的root是从其父转移来的.可以用树链剖分求lca:在dfs序上建立主席树将树上问题转变为区间问题,询问的时候用主席树求区间k小值. 终于能写出这种题了,开心! #include<bits/stdc++.h> using namespace std; const int maxn = 1e5+100; int n,m,e = 1,num,ans=0

BZOJ 3626: [LNOI2014]LCA [树链剖分 离线|主席树]

3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2050  Solved: 817[Submit][Status][Discuss] Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LC