图论--最近公共祖先问题(LCA)模板
最近公共祖先问题(LCA)是求一颗树上的某两点距离他们最近的公共祖先节点,由于树的特性,树上两点之间路径是唯一的,所以对于很多处理关于树的路径问题的时候为了得知树两点的间的路径,LCA是几乎最有效的解法。
首先是LCA的倍增算法。算法主体是依靠首先对整个树的预处理DFS,用来预处理出每个点的直接父节点,同时可以处理出每个点的深度和与根节点的距离,然后利用类似RMQ的思想处理出每个点的 2 的幂次的祖先节点,这就可以用 nlogn 的时间完成整个预处理的工作。然后每一次求两个点的LCA时只要对两个点深度经行考察,将深度深的那个利用倍增先爬到和浅的同一深度,然后一起一步一步爬直到爬到相同节点,就是LCA了。
具体模板是从鹏神的模板小改来的。
注释方便理解版:
- #include<stdio.h>
- #include<string.h>
- #include<algorithm>
- using namespace std;
- const int maxn=1e5+;
- const int maxm=1e5+;
- const int maxl=; //总点数的log范围,一般会开稍大一点
- int fa[maxl][maxn],dep[maxn],dis[maxn]; //fa[i][j]是j点向上(不包括自己)2**i 层的父节点,dep是某个点的深度(根节点深度为0),dis是节点到根节点的距离
- int head[maxn],point[maxm],nxt[maxm],val[maxm],size;
- int n;
- void init(){
- size=;
- memset(head,-,sizeof(head));
- }
- void add(int a,int b,int v){
- point[size]=b;
- val[size]=v;
- nxt[size]=head[a];
- head[a]=size++;
- point[size]=a;
- val[size]=v;
- nxt[size]=head[b];
- head[b]=size++;
- }
- void Dfs(int s,int pre,int d){ //传入当前节点标号,父亲节点标号,以及当前深度
- fa[][s]=pre; //当前节点的上一层父节点是传入的父节点标号
- dep[s]=d;
- for(int i=head[s];~i;i=nxt[i]){
- int j=point[i];
- if(j==pre)continue;
- dis[j]=dis[s]+val[i];
- Dfs(j,s,d+);
- }
- }
- void Pre(){
- dis[]=;
- Dfs(,-,);
- for(int k=;k+<maxl;++k){ //类似RMQ的做法,处理出点向上2的幂次的祖先。
- for(int v=;v<=n;++v){
- if(fa[k][v]<)fa[k+][v]=-;
- else fa[k+][v]=fa[k][fa[k][v]]; //处理出两倍距离的祖先
- }
- }
- }
- int Lca(int u,int v){
- if(dep[u]>dep[v])swap(u,v); //定u为靠近根的点
- for(int k=maxl-;k>=;--k){
- if((dep[v]-dep[u])&(<<k)) //根据层数差值的二进制向上找v的父亲
- v=fa[k][v];
- }
- if(u==v)return u; //u为v的根
- for(int k=maxl-;k>=;--k){
- if(fa[k][u]!=fa[k][v]){ //保持在相等层数,同时上爬寻找相同父节点
- u=fa[k][u];
- v=fa[k][v];
- }
- }
- return fa[][u]; //u离lca只差一步
- }
木有注释版:
- #include<stdio.h>
- #include<string.h>
- #include<algorithm>
- using namespace std;
- const int maxn=1e5+;
- const int maxm=1e5+;
- const int maxl=;
- int fa[maxl][maxn],dep[maxn],dis[maxn];
- int head[maxn],point[maxm],nxt[maxm],val[maxm],size;
- int n;
- void init(){
- size=;
- memset(head,-,sizeof(head));
- }
- void add(int a,int b,int v){
- point[size]=b;
- val[size]=v;
- nxt[size]=head[a];
- head[a]=size++;
- point[size]=a;
- val[size]=v;
- nxt[size]=head[b];
- head[b]=size++;
- }
- void Dfs(int s,int pre,int d){
- fa[][s]=pre;
- dep[s]=d;
- for(int i=head[s];~i;i=nxt[i]){
- int j=point[i];
- if(j==pre)continue;
- dis[j]=dis[s]+val[i];
- Dfs(j,s,d+);
- }
- }
- void Pre(){
- dis[]=;
- Dfs(,-,);
- for(int k=;k+<maxl;++k){
- for(int v=;v<=n;++v){
- if(fa[k][v]<)fa[k+][v]=-;
- else fa[k+][v]=fa[k][fa[k][v]];
- }
- }
- }
- int Lca(int u,int v){
- if(dep[u]>dep[v])swap(u,v);
- for(int k=maxl-;k>=;--k){
- if((dep[v]-dep[u])&(<<k))
- v=fa[k][v];
- }
- if(u==v)return u;
- for(int k=maxl-;k>=;--k){
- if(fa[k][u]!=fa[k][v]){
- u=fa[k][u];
- v=fa[k][v];
- }
- }
- return fa[][u];
- }
静态树上路径求最小值:LCA倍增
- #include<bits/stdc++.h>
- using namespace std;
- const int maxn=1e6+;
- const int maxm=2e6+;
- const int maxl=;
- const int INF = 0x3f3f3f3f;
- int fa[maxl][maxn],dep[maxn],dis[maxl][maxn];
- int head[maxn],point[maxm],nxt[maxm],val[maxm],size;
- int vis[maxn];
- int n,q,tmp=INF;
- void init(){
- size=;
- memset(head,-,sizeof(head));
- memset(vis,,sizeof(vis));
- }
- void add(int a,int b){
- point[size]=b;
- nxt[size]=head[a];
- head[a]=size++;
- point[size]=a;
- nxt[size]=head[b];
- head[b]=size++;
- }
- void Dfs(int s,int pre,int d){
- fa[][s]=pre;
- dis[][s]=s;
- dep[s]=d;
- for(int i=head[s];~i;i=nxt[i]){
- int j=point[i];
- if(j==pre)continue;
- Dfs(j,s,d+);
- }
- }
- void Pre(){
- Dfs(,-,);
- for(int k=;k+<maxl;++k){
- for(int v=;v<=n;++v){
- if(fa[k][v]<)fa[k+][v]=-;
- else fa[k+][v]=fa[k][fa[k][v]];
- if(fa[k][v]<)dis[k+][v]=dis[k][v];
- else dis[k+][v]=min(dis[k][v],dis[k][fa[k][v]]);
- }
- }
- }
- int Lca(int u,int v){
- tmp = min( u, v );
- if(dep[u]>dep[v])swap(u,v);
- for(int k=maxl-;k>=;--k){
- if((dep[v]-dep[u])&(<<k)){
- tmp = min( tmp, dis[k][v]);
- v=fa[k][v];
- }
- }
- tmp = min( tmp,v );
- if(u==v)return u;
- for(int k=maxl-;k>=;--k){
- if(fa[k][u]!=fa[k][v]){
- tmp=min(tmp,min(dis[k][u],dis[k][v]));
- u=fa[k][u],v=fa[k][v];
- }
- }
- tmp = min( tmp, min(u,v));
- tmp = min( tmp, fa[][u]);
- return fa[][u];
- }
- //tmp即为u、v路径上的最小值
离线Tarjan的做法主要是防止由于每个点对可能被询问多次,导致每次求都需要 logn 的时间,会超时,所以离线来一并处理所有的询问。
Tarjan的做法是通过递归到最底层,然后开始不断递归回去合并并查集,这样就能够在访问完每个点之后赋值它有关切另一个点已经被访问过的询问。
同样是鹏神的模板修改成自己的代码风格后的。
注释版:
- #include<stdio.h> //差不多要这些头文件
- #include<string.h>
- #include<vector>
- #include<algorithm>
- using namespace std;
- const int maxn=1e5+; //点数、边数、询问数
- const int maxm=2e5+;
- const int maxq=1e4+;
- int n;
- int head[maxn],nxt[maxm],point[maxm],val[maxm],size;
- int vis[maxn],fa[maxn],dep[maxn],dis[maxn];
- int ans[maxq];
- vector<pair<int,int> >v[maxn]; //记录询问、问题编号
- void init(){
- memset(head,-,sizeof(head));
- size=;
- memset(vis,,sizeof(vis));
- for(int i=;i<=n;++i){
- v[i].clear();
- fa[i]=i;
- }
- dis[]=dep[]=;
- }
- void add(int a,int b,int v){
- point[size]=b;
- val[size]=v;
- nxt[size]=head[a];
- head[a]=size++;
- point[size]=a;
- val[size]=v;
- nxt[size]=head[b];
- head[b]=size++;
- }
- int find(int x){
- return x==fa[x]?x:fa[x]=find(fa[x]);
- }
- void Tarjan(int s,int pre){
- for(int i=head[s];~i;i=nxt[i]){
- int j=point[i];
- if(j!=pre){
- dis[j]=dis[s]+val[i];
- dep[j]=dep[s]+;
- Tarjan(j,s); //这里Tarjan的DPS操作必须在并查集合并之前,这样才能保证求lca的时候lca是每一小部分合并时的祖先节点,如果顺序交换,那么所有的查询都会得到 1 节点,就是错误的
- int x=find(j),y=find(s);
- if(x!=y)fa[x]=y;
- }
- }
- vis[s]=;
- for(int i=;i<v[s].size();++i){
- int j=v[s][i].first;
- if(vis[j]){
- int lca=find(j);
- int id=v[s][i].second;
- ans[id]=lca; //这里视题目要求给答案赋值
- // ans[id]=dep[s]+dep[j]-2*dep[lca];
- // ans[id]=dis[s]+dis[j]-2*dis[lca];
- }
- }
- }
- for(int i=;i<=k;++i){ //主函数中的主要部分
- int a,b;
- scanf("%d%d",&a,&b);
- v[a].push_back(make_pair(b,i)); //加问题的时候两个点都要加一次
- v[b].push_back(make_pair(a,i));
- }
- Tarjan(,);
木有注释版:
- #include<stdio.h>
- #include<string.h>
- #include<vector>
- #include<algorithm>
- using namespace std;
- const int maxn=1e5+;
- const int maxm=2e5+;
- const int maxq=1e4+;
- int n;
- int head[maxn],nxt[maxm],point[maxm],val[maxm],size;
- int vis[maxn],fa[maxn],dep[maxn],dis[maxn];
- int ans[maxq];
- vector<pair<int,int> >v[maxn];
- void init(){
- memset(head,-,sizeof(head));
- size=;
- memset(vis,,sizeof(vis));
- for(int i=;i<=n;++i){
- v[i].clear();
- fa[i]=i;
- }
- dis[]=dep[]=;
- }
- void add(int a,int b,int v){
- point[size]=b;
- val[size]=v;
- nxt[size]=head[a];
- head[a]=size++;
- point[size]=a;
- val[size]=v;
- nxt[size]=head[b];
- head[b]=size++;
- }
- int find(int x){
- return x==fa[x]?x:fa[x]=find(fa[x]);
- }
- void Tarjan(int s,int pre){
- for(int i=head[s];~i;i=nxt[i]){
- int j=point[i];
- if(j!=pre){
- dis[j]=dis[s]+val[i];
- dep[j]=dep[s]+;
- Tarjan(j,s);
- int x=find(j),y=find(s);
- if(x!=y)fa[x]=y;
- }
- }
- vis[s]=;
- for(int i=;i<v[s].size();++i){
- int j=v[s][i].first;
- if(vis[j]){
- int lca=find(j);
- int id=v[s][i].second;
- ans[id]=lca;
- // ans[id]=dep[s]+dep[j]-2*dep[lca];
- // ans[id]=dis[s]+dis[j]-2*dis[lca];
- }
- }
- }
- for(int i=;i<=k;++i){
- int a,b;
- scanf("%d%d",&a,&b);
- v[a].push_back(make_pair(b,i));
- v[b].push_back(make_pair(a,i));
- }
- Tarjan(,);
另外,现在又有LCA用dfs序+RMQ的做法,可以实现O(nlogn)预处理,O(1)查询的LCA,基本可以完全替代倍增LCA和TarjanLCA,但是树上路径长度和树上路径最小值无法用这个来做。
- #include <bits/stdc++.h>
- using namespace std;
- const int maxn = 2e5+;
- const int maxl = ;
- int vis[maxn],dep[maxn],dp[maxn][maxl];
- int head[maxn],in[maxn],id[maxn];
- int point[maxn],nxt[maxn],sz;
- int val[maxn];
- int fa[maxl][maxn]; //fa[i][j]是j点向上(不包括自己)2**i 层的父节点,dep是某个点的深度(根节点深度为0),dis是节点到根节点的距离
- int n;
- void init(){
- sz = ;
- memset(head,-,sizeof(head));
- memset(fa,-,sizeof(fa));
- }
- void Pre(){
- for(int k=;k+<maxl;++k){ //类似RMQ的做法,处理出点向上2的幂次的祖先。
- for(int v=;v<=n;++v){
- if(fa[k][v]<)fa[k+][v]=-;
- else fa[k+][v]=fa[k][fa[k][v]]; //处理出两倍距离的祖先
- }
- }
- }
- void dfs(int u,int p,int d,int&k){
- fa[][u]=p; //当前节点的上一层父节点是传入的父节点标号
- vis[k] = u;
- id[u] = k;
- dep[k++]=d;
- for(int i = head[u];~i;i=nxt[i]){
- int v = point[i];
- if(v == p)continue;
- dfs(v,u,d+,k);
- vis[k] = u;
- dep[k++]=d;
- }
- }
- void RMQ(int root){
- int k = ;
- dfs(root,-,,k);
- int m = k;
- int e= (int)(log2(m+1.0));
- for(int i = ; i < m ; ++ i)dp[i][]=i;
- for(int j = ; j <= e ; ++ j){
- for(int i = ; i + ( << j ) - < m ; ++ i){
- int N = i + (<<(j-));
- if(dep[dp[i][j-]] < dep[dp[N][j-]]){
- dp[i][j] = dp[i][j-];
- }
- else dp[i][j] = dp[N][j-];
- }
- }
- }
- void add(int a,int b){
- point[sz] = b;
- nxt[sz] = head[a];
- head[a] = sz++;
- }
- int LCA(int u,int v){
- int left = min(id[u],id[v]),right = max(id[u],id[v]);
- int k = (int)(log2(right- left+1.0));
- int pos,N = right - (<<k)+;
- if(dep[dp[left][k]] < dep[dp[N][k]])pos = dp[left][k];
- else pos = dp[N][k];
- return vis[pos];
- }
- int q;
- inline int get(int a,int k){
- int res = a;
- for(int i = ; (1ll << i ) <= k ; ++ i){
- if(k&(1ll<<i)){
- res = fa[i][res];
- }
- }
- return res;
- }
- void run(){
- while(q--){
- int a,b,k;
- scanf("%d%d%d",&a,&b,&k);
- int lca = LCA(a,b);
- int num = dep[id[a]] - dep[id[lca]] + dep[id[b]] - dep[id[lca]] + ;
- int up = (num - )%k;
- int ans = val[a];
- // printf("a : %d\n",a);
- while(dep[id[a]] - dep[id[lca]] >= k){
- int Id = get(a,k);
- ans ^= val[Id];
- // printf("a : %d\n",Id);
- a = Id;
- }
- if(dep[id[b]] - dep[id[lca]] > up){
- // printf("up: %d\n",up);
- if(up == )ans^= val[b];
- b = get(b,up);
- while(dep[id[b]] - dep[id[lca]] >k ){
- int Id = get(b,k);
- ans ^= val[Id];
- b = Id;
- }
- }
- printf("%d\n",ans);
- }
- }
- int main(){
- while(scanf("%d%d",&n,&q)!=EOF){
- init();
- for(int i = ; i < n; ++ i){
- int a,b;
- scanf("%d%d",&a,&b);
- add(a,b);
- add(b,a);
- }
- for(int i = ;i <= n ; ++ i)scanf("%d",&val[i]);
- RMQ();
- Pre();
- run();
- }
- return ;
- }
图论--最近公共祖先问题(LCA)模板的更多相关文章
- 洛谷P3379 【模板】最近公共祖先(LCA)
P3379 [模板]最近公共祖先(LCA) 152通过 532提交 题目提供者HansBug 标签 难度普及+/提高 提交 讨论 题解 最新讨论 为什么还是超时.... 倍增怎么70!!题解好像有 ...
- P3379 【模板】最近公共祖先(LCA)
P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...
- 洛谷P3379 【模板】最近公共祖先(LCA)(dfs序+倍增)
P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...
- 「LuoguP3379」 【模板】最近公共祖先(LCA)
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- 洛谷——P3379 【模板】最近公共祖先(LCA)
P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...
- LCA 最近公共祖先 (笔记、模板)
求lca的方法大体有三种: 1.dfs+RMQ(线段树 ST表什么的) 在线 2.倍增 在线 3.tarjan 离线 ps:离线:所有查询全输入后一次解决 在线:有一个查询输出一次 以下模板题为 洛谷 ...
- luogo p3379 【模板】最近公共祖先(LCA)
[模板]最近公共祖先(LCA) 题意 给一个树,然后多次询问(a,b)的LCA 模板(主要参考一些大佬的模板) #include<bits/stdc++.h> //自己的2点:树的邻接链表 ...
- 【原创】洛谷 LUOGU P3379 【模板】最近公共祖先(LCA) -> 倍增
P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...
- P3379 【模板】最近公共祖先(LCA)(欧拉序+rmq)
P3379 [模板]最近公共祖先(LCA) 用欧拉序$+rmq$维护的$lca$可以做到$O(nlogn)$预处理,$O(1)$查询 从这里剻个图 #include<iostream> # ...
- 最近公共祖先(LCA)学习笔记 | P3379 【模板】最近公共祖先(LCA)题解
研究了LCA,写篇笔记记录一下. 讲解使用例题 P3379 [模板]最近公共祖先(LCA). 什么是LCA 最近公共祖先简称 LCA(Lowest Common Ancestor).两个节点的最近公共 ...
随机推荐
- 《BI项目笔记》创建多维数据集Cube(2)
本节建立: 历年的初烟水分均值变化分析Cube:区域维度:地州,专县时间维度:年等级维度:大等级,小等级指标:水分均值 数据源视图: 数据处理: ) ) DELETE FROM T_QualMoist ...
- golang csv问题
go语言自带的有csv文件读取模块,看起来好像不错,今天玩玩,也算是系统学习go语言的一部分--^_^ 一.写csv文件 函数: func NewWriter(w io.Writer) *Writer ...
- javascript事件之:谈谈自定义事件
对于JavaScript自定义事件,印象最深刻的是用jQuery在做图片懒加载的时候.给需要懒加载的图片定义一个appear事件.当页面图片开始出现时候,触发这个自定义的appear事件(注意,这里只 ...
- Deep Learning 6_深度学习UFLDL教程:Softmax Regression_Exercise(斯坦福大学深度学习教程)
前言 练习内容:Exercise:Softmax Regression.完成MNIST手写数字数据库中手写数字的识别,即:用6万个已标注数据(即:6万张28*28的图像块(patches)),作训练数 ...
- USACO2011Brownie Slicing巧克力蛋糕切片
Description Bessie烘焙了一块巧克力蛋糕.这块蛋糕是由R*C(1 <= R,C <= 500)个小的巧克力蛋糕组成的. 第i行,第j列的蛋糕有N_ij(1 < ...
- t检验
例子:以往通过大规模调查已知某地新生儿出生体重为3.30kg.从该地难产儿中随机抽取35名新生儿作为研究样本,平均出生体重为3.42kg,标准差为0.40kg. 问该地难产儿出生体重是否与一般新生儿体 ...
- 浅谈HTTP协议(上)
今天讨论一下HTTP协议.一个做前端的,如果连HTTP协议都不了解,那实在是太不合格了. 首先,什么是HTTP?Hyper Text Transfer Protocol(超文本传输协议),用在浏览器和 ...
- js+css3文字模糊代码
在写文字模糊的时候要理清自己的思路,根据以下的步骤来: 对你要模糊的文字进行布局 <body style="background:#ccc;"> <ul clas ...
- 关于在biweb 中安装完成后 首页上方报错问题的解决
在利用biweb进行网站开发的时候 首先得安装biweb 安装就是下一步,,,下一步....下一步 最后就成功了 .但是有种情况我总是竟让遇到,而已有的人安装会遇到 有的人安装就不会遇到,后 ...
- iOS开发UI篇—简单的浏览器查看程序
iOS开发UI篇—简单的浏览器查看程序 一.程序实现要求 1.要求 2. 界面分析 (1) 需要读取或修改属性的控件需要设置属性 序号标签 图片 图片描述 左边按钮 右边按钮 (2) 需要监听响应事件 ...