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(最近公共祖先)专题(不定期更新)的更多相关文章

  1. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  2. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

  3. LCA(最近公共祖先)模板

    Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...

  4. CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )

    CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...

  5. LCA近期公共祖先

    LCA近期公共祖先 该分析转之:http://kmplayer.iteye.com/blog/604518 1,并查集+dfs 对整个树进行深度优先遍历.并在遍历的过程中不断地把一些眼下可能查询到的而 ...

  6. LCA 近期公共祖先 小结

    LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...

  7. Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】

    一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...

  8. (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)

    基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...

  9. D5 LCA 最近公共祖先

    第一题: POJ 1330 Nearest Common Ancestors POJ 1330 这个题可不是以1为根节点,不看题就会一直wa呀: 加一个找根节点的措施: #include<alg ...

随机推荐

  1. Stage3D&Away3D整理ppt

    资料下载地址:http://files.cnblogs.com/xignzou/away3D%E6%A1%86%E6%9E%B6%E5%AD%A6%E4%B9%A0%E5%88%86%E4%BA%AB ...

  2. InputArray和OutputArray

    源码路径:~/opencv-2.4.9/modules/core/include/opencv2/core/core.hpp where _InputArray is a class that can ...

  3. jquery插件实现分页

    Query Pagination分页插件 原项目地址:http://plugins.jquery.com/project/pagination 版本:v1.2 源文件下载:英文原版 或 中文翻译修改版 ...

  4. spring boot 多层级mapper

    mapper目录结构:   mapper ----dev -------produce   在 application.properties 文件中配置 mybatis.mapper-location ...

  5. 喜闻乐见的const int *p、int* const p、const int* const p

    不废话直接代码示例: void f(const int *p) { ; *p = ; // error p = &b; // fine } void f(int* const p) { ; * ...

  6. FAT32和NTFS最大支持的单个文件大小分别是多大?

    以前,RAID控制器和文件系统都有2TB的限制.控制器上的限制,主要是指每个LUN不能大于2TB,而磁盘组一般则没有这个限制:文件系统的限制主要是指每个分区(partition)不能大于2TB,不过3 ...

  7. js和jquery触发按钮点击事件

    js触发按钮点击事件 function load(){ //下面两种方法效果是一样的 document.getElementById("target").onclick(); do ...

  8. object is not an instance of declaring class

    错误原因:invoke方法的时候,应该是类的实例对象,而不是类本身 解决方法:把 PowerMockito.doReturn(index_expect).when(IndexController.cl ...

  9. AtCoder Regular Contest 070 D - No Need 想法:利用单调性二分+bitset优化

    /** 题目:D - No Need 链接:http://arc070.contest.atcoder.jp/tasks/arc070_b 题意:给出N个数,从中选出一个子集,若子集和大于等于K,则这 ...

  10. Redis快速入门及实现

    redis的概念 (1)Redis的优点 以下是Redis的一些优点. 异常快 - Redis非常快,每秒可执行大约110000次的设置(SET)操作,每秒大约可执行81000次的读取/获取(GET) ...