图论--最近公共祖先问题(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).两个节点的最近公共 ...
随机推荐
- Knockout 新版应用开发教程之创建view models与监控属性
最近抽出点时间研究MVVM,包括司徒正美的avalon,google的angular,以及Knockout,博客园Tom的Knockout指南 时隔2年了,ko更新了不少,所以文档也相应的变化了,所以 ...
- CSS的压缩 方法与解压
为什么要压缩CSS? 1.大网站节约流量 2.加快访问速度 工具:Dreamweaver(手工替换,个人感觉任何文本编辑器都可以)不过DW可以还原 CSS压缩与CSS代码压缩还原方法,CSS压缩工具有 ...
- Delphi 使用之连接数据库
DELPHI 中的数据库开发有很多种类的,可以连接Access数据库.MS SQL Server 数据库.Oracle 数据库.MySQL数据库等等,一般连接有两种方式:BDE和ADO两种方式, 都是 ...
- Server asks us to fall back to SIMPLE auth, but this client is configured to only allow secure connections.
我是在flume向hdfs 写(sink)数据时遇到的这个错误. Server (是指hdfs) asks us to fall back to SIMPLE auth, but this clien ...
- remount failed: Operation not permitted ,怎么办呢?
remount failed: Operation not permitted ,怎么办呢? 1. 确定是否正确连接手机了$ adb devices 2. 进入shell$ adb shell 3. ...
- Mysqldump源码分析
版权声明:本文由王珏原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/261 来源:腾云阁 https://www.qclou ...
- android定义启动唯一apk
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="ht ...
- (41) Aeroo 模板设计基础教程
1. 理论基础 注:我采用libreoffice5.2设计讲解 1.1. 定义模板指令 模板指令的语法和Genshi 模板语言相兼容,可以用Libreoffice( Write, Ca ...
- 算法课堂笔记13—Online Algorithm
今天的算法课是学习离线算法,在计算机科学中,一个在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入.相对的,对于一个离线算法,在开始时就需要知道问题的所有输入 ...
- logcat的条数设置
在软件默认设置下,logcat的缓存为1024,即logcat显示的条数有限,给程序的调试带来很大的不便,通过设置 logcat缓存的大小,可以增加logcat显示的条数,将程序调试的输出都可以打印出 ...