LCA(最近公共祖先)专题(不定期更新)
Tarjan(离线)算法
思路:
1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.合并v到u上。
5.寻找与当前点u有询问关系的点v。
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
1、POJ 1330 Nearest Common Ancestors
题意:给出一颗有根树(外向树),再给出有向边。询问一次,求两点的最近公共祖先。
思路:最最基础的LCA题目,而且只询问一次。对于外向树,记录入度,入度为0的则为根。
①tarjan离线算法实现
#include<iostream>
#include<cstdio>
#include<vector>
#include<memory.h>
using namespace std;
const int maxn = ;
const int maxq = ;
vector<int> node[maxn];//邻接表
int q1, q2, ans;
int n, qnum;
int index[maxn];//入度
int pre[maxn];//并查集
bool vis[maxn];//标记是否访问过
pair<int, int>queq[maxq];//保存查询顺序
int f[maxn];//保存临时祖先
int Find(int x)
{
int r = x;
while (pre[r] != r)
{
r = pre[r];
}
int i = x, j;
while (i != r)
{
j = pre[i];
if (j != r) pre[i] = r;
i = j;
}
return r;
}
void Join(int root, int child)
{
int rx = Find(root), ry = Find(child);
if (rx != ry) pre[child] = root;
}
void LCA(int root)
{
f[Find(root)] = root;
vis[root] = true;//标记被访问过
int sz = node[root].size();
for (int i = ; i < sz; i++)
{//访问所有root子节点
if (!vis[node[root][i]])
{
LCA(node[root][i]);//继续往下遍历
Join(root, node[root][i]);//合并
f[Find(root)] = root;
}
}
if (q1 == root&&vis[q2]) ans = f[Find(q2)];
else if (q2 == root&&vis[q1]) ans = f[Find(q1)];
}
void Init()//初始化
{
for (int i = ; i <= n; i++)
{
node[i].clear();
pre[i] = i;
}
memset(vis, , sizeof(vis));
memset(f, , sizeof(f));
memset(index, , sizeof(index));
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
Init();
for (int i = ; i <= n - ; i++)
{
int u, v;
scanf("%d%d", &u, &v);
node[u].push_back(v);//有向图
index[v]++;
}
scanf("%d%d", &q1, &q2);
int root = ;
for (; root <= n; root++) if (!index[root]) break;//入度为0的为根
LCA(root);
printf("%d", ans);
printf("\n");
}
return ;
}
tarjan离线算法
2、hdu 2874 Connections between cities
题意:给出一片森林(有多棵树),询问若干次,如果两点是连通的,求其最短路径长度。
思路:tarjan离线算法
①要用边表来存储每条边和询问的点。用邻接表MLE。
②dis[]数组保存的是该点到根的距离(由于是树,所以每两点之间只有一条路径,且其为最短路径)。那么对于一棵树里的两点u,v,u、v之间的最短路径长度为dis[v]+dis[u]-2*dis[nf],nf为该两点的最近公共祖先。
#include<iostream>
#include<memory.h>
#include<vector>
using namespace std;
int n, m, c;
const int maxn = ;
const int maxe = ;
const int maxq = ;
//用边表来存各条边,邻接表MLE
int Head[maxn];
struct edge
{
int to;
int len;
int next;
}eg[*maxe];
//用边表来存每次询问,邻接表MLE
int qhead[maxn];
struct qry
{
int to;
int next;
}qy[*maxq];
int ans[maxq];//存第i个询问的结果
int dis[maxn];//存各棵树下结点到根的距离
int vis[maxn];//是否访问标记,不为0时其值代表结点所在的树编号
int pre[maxn];//并查集
int f[maxn];//存祖先
void Init()
{
for (int i = ; i <= n; i++)
{
pre[i] = i;
}
memset(qhead, , sizeof(qhead));
memset(qy, , sizeof(qy));
memset(eg, , sizeof(eg));
memset(Head, , sizeof(Head));
memset(ans, , sizeof(ans));
memset(dis, , sizeof(dis));
memset(vis, , sizeof(vis));
memset(f, , sizeof(f));
}
int Findf(int x)
{
int r = x;
while (r != pre[r])
{
r = pre[r];
}
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void Join(int x, int y)
{
int rx = Findf(x), ry = Findf(y);
if (rx != ry) pre[x] = y;
}
void LCA(int rt, int k)
{
vis[rt] = k;
f[rt] = rt;
for (int i = Head[rt]; i!=; i=eg[i].next)
{
int u = eg[i].to;
if (!vis[u])
{
dis[u] = dis[rt] + eg[i].len;
LCA(u, k);
Join(u, rt);
f[Findf(rt)] = rt;
}
}
for (int i = qhead[rt]; i != ; i = qy[i].next)
{
int v = qy[i].to;
if (vis[v] && vis[v] == vis[rt])
{
ans[(i+) / ] = dis[rt] + dis[v] - * dis[Findf(v)];
}
else ans[(i+) / ] = -;
} }
int main()
{
while (~scanf("%d%d%d", &n, &m, &c))
{
Init();
int cnt = ;
for (int i = ; i <= m; i++)
{
int u, v, len;
scanf("%d%d%d", &u, &v, &len);
eg[cnt].to = v, eg[cnt].len = len,eg[cnt].next=Head[u],Head[u]=cnt;
cnt++;
eg[cnt].to = u, eg[cnt].len = len, eg[cnt].next = Head[v], Head[v] = cnt;
cnt++;
}
cnt = ;
for (int i = ; i <= c; i++)
{
int u, v;
scanf("%d%d", &u, &v);
qy[cnt].to = v, qy[cnt].next = qhead[u], qhead[u] = cnt;
cnt++;
qy[cnt].to = u, qy[cnt].next = qhead[v], qhead[v] = cnt;
cnt++;//这样保存(cnt+1)/2则为其询问编号
}
int k = ;
for (int i = ; i <= n; i++)
{
if (!vis[i])
{
LCA(i, k);
k++;
}
}
for (int i = ; i <= c; i++)
{
if (ans[i] == -) printf("Not connected\n");
else printf("%d\n", ans[i]);
}
}
return ;
}
3、hdu 4547 CD操作
题意:从子目录到父目录需要一级一级向上,从父目录只需要一步就能到子目录。给出n-1个相差一级的<子目录,父目录>,询问m次从a目录到b目录的最小步数。
思路:tarjan离线算法
①由于目录名称为字符串,用string来存,建立映射,即为每个目录名称建立对应的唯一编号,之后用编号来进行对应处理。
②用边表存储每条有向边;用边表存储询问对(反着也要存一遍)
③dis[]数组保存的是该点到根的距离,每条有向边的长度为1.如果询问的序号i为奇数(没有反,从rt到v),则ans[(i+1)/2]=dis[rt]-dis[rf]+(rf==v?0:1);否则表示从v到rt,则ans[(i+1)/2]=dis[v]-dis[rf]+(rf==rt?0:1).
此处rf为rt和v的最近公共祖先。
#include<iostream>
#include<memory.h>
#include<vector>
#include<map>
#include<string>
using namespace std;
int n, m, c;
map<string, int>mp;
const int maxn = ;
const int maxe = ;
const int maxq = ;
int in[maxn];
//用边表来存各条边
int Head[maxn];
struct edge
{
int to;
int len;
int next;
}eg[maxe];
//用边表来存每次询问
int qhead[maxn];
struct qry
{
int to;
int next;
}qy[ * maxq];
int ans[maxq];//存第i个询问的结果
int dis[maxn];//存结点到根的距离
int vis[maxn];//是否访问标记
int pre[maxn];//并查集
int f[maxn];//存祖先
void Init()
{
for (int i = ; i <= n; i++)
{
pre[i] = i;
}
mp.clear();
memset(in, , sizeof(in));
memset(qhead, , sizeof(qhead));
memset(qy, , sizeof(qy));
memset(eg, , sizeof(eg));
memset(Head, , sizeof(Head));
memset(ans, , sizeof(ans));
memset(dis, , sizeof(dis));
memset(vis, , sizeof(vis));
memset(f, , sizeof(f));
}
int Findf(int x)
{
int r = x;
while (r != pre[r])
{
r = pre[r];
}
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void Join(int x, int y)
{
int rx = Findf(x), ry = Findf(y);
if (rx != ry) pre[x] = y;
}
void LCA(int rt)
{
vis[rt] = ;
f[rt] = rt;
for (int i = Head[rt]; i != ; i = eg[i].next)
{
int u = eg[i].to;
if (!vis[u])
{
dis[u] = dis[rt] + eg[i].len;
LCA(u);
Join(u, rt);
f[Findf(rt)] = rt;
}
}
for (int i = qhead[rt]; i != ; i = qy[i].next)
{
int v = qy[i].to;
if (vis[v])
{
int rf = Findf(v);
if(i%==)ans[(i+)/] = dis[rt] - dis[rf] + (v== rf ? : );
else ans[(i+)/] = dis[v] - dis[rf] + (rt == rf ? : );
}
} }
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &m);
Init();
string a, b;
int k = ;
for (int i = ; i <= n-; i++)
{
int u, v;
cin >> a >> b;
if (!mp[a]) mp[a] = ++k;
if (!mp[b]) mp[b] = ++k;
u = mp[b], v = mp[a];//从b到a
in[v]++;
eg[i].to = v, eg[i].len = , eg[i].next = Head[u], Head[u] = i;
}
int cnt = ;
for (int i = ; i <= m; i++)
{
int u, v;
cin >> a >> b;
u = mp[a], v = mp[b];//从a到b
qy[cnt].to = v, qy[cnt].next = qhead[u], qhead[u] = cnt;//奇数从a到b
cnt++;
qy[cnt].to = u, qy[cnt].next = qhead[v], qhead[v] = cnt;//偶数从b到a
cnt++; }
for (int i = ; i <= k; i++)
{
if (!in[i])
{
LCA(i);
break;
}
}
for (int i = ; i <=m; i++)
{
printf("%d\n", ans[i]);
}
}
return ;
}
4、hdu 6115 Factory
题意:给出无向图,给出每个公司的子公司的位置,若干次询问两个公司之间的最短距离(即两个公司所有子公司之间的最短距离)。
思路:LCA在线ST算法模板。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
//#pragma comment(linker, "/STACK:102400000,102400000") //不需要申请系统栈
const int N = ;
const int M = ;
int dp[ * N][M]; //这个数组记得开到2*N,因为遍历后序列长度为2*n-1
bool vis[N];
struct edge
{
int u, v, w, next;
}e[ * N];
int tot, head[N];
inline void add(int u, int v, int w, int &k)
{
e[k].u = u; e[k].v = v; e[k].w = w;
e[k].next = head[u]; head[u] = k++;
u = u^v; v = u^v; u = u^v;
e[k].u = u; e[k].v = v; e[k].w = w;
e[k].next = head[u]; head[u] = k++;
}
int ver[ * N], R[ * N], first[N], dir[N];
//ver:节点编号 R:深度 first:点编号位置 dir:距离
void dfs(int u, int dep)
{
vis[u] = true; ver[++tot] = u; first[u] = tot; R[tot] = dep;
for (int k = head[u]; k != -; k = e[k].next)
if (!vis[e[k].v])
{
int v = e[k].v, w = e[k].w;
dir[v] = dir[u] + w;
dfs(v, dep + );
ver[++tot] = u; R[tot] = dep;
}
}
void ST(int n)
{
for (int i = ; i <= n; i++)
dp[i][] = i;
for (int j = ; ( << j) <= n; j++)
{
for (int i = ; i + ( << j) - <= n; i++)
{
int a = dp[i][j - ], b = dp[i + ( << (j - ))][j - ];
dp[i][j] = R[a]<R[b] ? a : b;
}
}
}
//中间部分是交叉的。
int RMQ(int l, int r)
{
int k = ;
while (( << (k + )) <= r - l + )
k++;
int a = dp[l][k], b = dp[r - ( << k) + ][k]; //保存的是编号
return R[a]<R[b] ? a : b;
} int LCA(int u, int v)
{
int x = first[u], y = first[v];
if (x > y) swap(x, y);
int res = RMQ(x, y);
return ver[res];
}
vector<int>cp[N];
int main()
{
int cas;
scanf("%d", &cas);
while (cas--)
{
int n,m, q, num = ;
scanf("%d%d", &n, &m);
memset(head, -, sizeof(head));
memset(vis, false, sizeof(vis));
for (int i = ; i<n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w, num);
}
tot = ; dir[] = ;
dfs(, );
/*printf("节点ver "); for(int i=1; i<=2*n-1; i++) printf("%d ",ver[i]); cout << endl;
printf("深度R "); for(int i=1; i<=2*n-1; i++) printf("%d ",R[i]); cout << endl;
printf("首位first "); for(int i=1; i<=n; i++) printf("%d ",first[i]); cout << endl;
printf("距离dir "); for(int i=1; i<=n; i++) printf("%d ",dir[i]); cout << endl;*/
ST( * n - );
for (int i = ; i <= m; i++)
{
cp[i].clear();
int g;
scanf("%d", &g);
for (int j = ; j < g; j++)
{
int v;
scanf("%d", &v);
cp[i].push_back(v);
}
}
scanf("%d", &q); while (q--)
{
int u, v;
scanf("%d%d", &u, &v);
int ans = 0x3f3f3f3f;
for (int i = ; i < cp[u].size(); i++)
{
for (int j = ; j < cp[v].size(); j++)
{
int uu = cp[u][i];
int vv = cp[v][j];
int lca = LCA(uu, vv);
ans = min(ans, dir[uu] + dir[vv] - * dir[lca]);
}
}
printf("%d\n", ans);
}
}
return ;
}
LCA(最近公共祖先)专题(不定期更新)的更多相关文章
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
		Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ... 
- lca 最近公共祖先
		http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ... 
- LCA(最近公共祖先)模板
		Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ... 
- CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )
		CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ... 
- LCA近期公共祖先
		LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ... 
- LCA 近期公共祖先 小结
		LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ... 
- Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】
		一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ... 
- (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)
		基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ... 
- D5 LCA 最近公共祖先
		第一题: POJ 1330 Nearest Common Ancestors POJ 1330 这个题可不是以1为根节点,不看题就会一直wa呀: 加一个找根节点的措施: #include<alg ... 
随机推荐
- Android JNI和NDK学习(06)--JNI的数据类型(转)
			本文转自:http://www.cnblogs.com/skywang12345/archive/2013/05/23/3094037.html 本文介绍JNI的数据类型.NDK中关于JNI数据类型的 ... 
- 李洪强iOS下的实际网络连接状态检测
			iOS下的实际网络连接状态检测 序言 网络连接状态检测对于我们的iOS app开发来说是一个非常通用的需求.为了更好的用户体验,我们会在无网络时展现本地或者缓存的内容,并对用户进行合适的提示.对绝大部 ... 
- python 面向对象类成员(字段 方法 属性)
			一.字段 字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同, 普通字段属于对象 静态字段属于类 class Province: # 静态字段 countr ... 
- 扒一扒asp.net core mvc控制器的寻找流程
			不太会排版,大家将就看吧. asp.net core mvc和asp.net mvc中都有一个比较有意思的而又被大家容易忽略的功能,控制器可以写在非Web程序集中,比如Web程序集:"MyW ... 
- git分支管理与冲突解决(转载)
			Git 分支管理和冲突解决 原文:http://www.cnblogs.com/mengdd/p/3585038.html 创建分支 git branch 没有参数,显示本地版本库中所有的本地分支名称 ... 
- Laravel 的中大型专案架构
			好文:http://oomusou.io/laravel/laravel-architecture/ 
- hdu 3667 (拆边 mcmf)
			注意题目中 边的容量 <= 5.可以把费用权值 a *f ^2化归成 a * f2, 即第一条边费用为 1 * a, 第二条 为 (4 - 1) * a, 第三条为 (9 - 4) * a.. ... 
- <转> 堆和栈的区别
			一.预备知识—程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等.其操作方式类似于数 ... 
- mvc 二级域名 重定向
			使用mvc开发了一个独立的站点(wechat),但是最后要和并到另外一个站点下(admin),但是外部访问要使用另一个站点(admin)的二级域名 考虑之后采用mvc路由机制来实现(这也要考虑),代码 ... 
- 【BZOJ3730】震波 动态树分治+线段树
			[BZOJ3730]震波 Description 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土 ... 
