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 ...
随机推荐
- 使用 ObjectDataSource 缓存数据
简介 就计算机科学而言 , 缓存 过程包括成本昂贵的数据或信息的获取 , 以及将备份存储在可快速访问的位置.对于数据驱动的应用程序,大型.复杂的查询通常会消耗大量应用程序执行时间.要提升这类应用程序的 ...
- 142. Linked List Cycle II【easy】
142. Linked List Cycle II[easy] Given a linked list, return the node where the cycle begins. If ther ...
- sendmail发件人邮箱设定命令
sendmail发件人邮箱设定命令 以前就碰到过设置发件人后缀的方式,这次迁移服务器居然忘记,从头开始记录下 1:第一种方法,修改/etc/hosts,据说sendmail使用hosts里面的本地 ...
- 5 月 35 日临近,Google 无法访问,可以使用 Google IP 来解决。
每年都会有几天那啥,你懂的. 直接使用 Google 的域名访问经常会打不开,而使用 Google 的 IP 就会很顺畅. 使用 Chrome 浏览器我们经常都会在地址栏直接搜索,所以我们只要添加一个 ...
- CentOS 7 安装以及配置桌面环境
一.安装 GNOME 桌面 1.安装命令: yum groupinstall "GNOME Desktop" "X Window System" " ...
- tuple 元组
创建单元素tuple tuple和list一样,可以包含 0 个.1个和任意多个元素. 包含多个元素的 tuple,前面我们已经创建过了. 包含 0 个元素的 tuple,也就是空tuple,直接用 ...
- Windows Service的转换与部署
开发Windows Service,可能会碰到以下两种情况. 1. 直接开发一个Windows Service 网上有很多教程,可以参考这个: http://www.cnblogs.com/sorex ...
- tcpdump http://www.cnblogs.com/daisin/articles/5512957.html
http://www.cnblogs.com/daisin/articles/5512957.html
- Linux Linux常用命令二
whoami 我是谁命令 --该命令用户查看当前系统当前账号的用户名 --由于系统管理员通常需要使用多种身份登录系统,李儒通常使用普通用户登录系统,然后再以su命令切换到root身份对系统进行灌篮.这 ...
- HTTP 请求:GET vs. POST
两种在客户端和服务器端进行请求-响应的常用方法是:GET 和 POST. GET - 从指定的资源请求数据 POST - 向指定的资源提交要处理的数据 GET 基本上用于从服务器获得(取回)数据.注释 ...