LCA 之Tarjan
最近回顾之前学的算法,今天之后冲刺网络流,后缀函数,后缀自动机,回文自动机,二分图,树链剖分,树论进阶,以及数学/数论这块。
先说说LCA,LCA最近公共祖先,有三种算法,一种是朴素算法,就是最简单的两个点往上找祖宗。第二种是基于st表的优化,第三种就是tarjan了,是一个把问题先处理好再输出的算法(离线算法)。
一、并查集,Tarjan是基于深搜的,并查集存储父节点信息。
这个是查询函数:
int find(int x)
{
if (x == fa[x])
return x;
return fa[x] = find(fa[x]);
}
二、存边,存查询,存边用的链式前向星,存查询也用链式前向星,这里关键点在于,第i个查询的比如是x,y的公共祖先,那么把y,i这两个条件存在x的链表下,把x,i存在y的链表下
代码实现如下:
struct edge
{
int to, next;
} e[N << 1];
struct q
{
int to, num, next;
} q[N << 1];
void add(int x, int y)
{
e[++cnt].to = y;
e[cnt].next = h[x];
h[x] = cnt;
}
void qq(int x, int y, int num)
{
q[++cnt2].to = y;
q[cnt2].next = qh[x];
q[cnt2].num = num;
qh[x] = cnt2;
}
第三部分也是最关键的思想:
void tarjan(int u)
{
vis[u] = 1;
for (int i = h[u]; i; i = e[i].next)
if (!vis[e[i].to])
{
tarjan(e[i].to);
fa[e[i].to] = u;
} //1
for (int i = qh[u]; i; i = q[i].next)
if (vis[q[i].to])
{
ans[q[i].num] = find(q[i].to);
}//2
}
第一个for循环是遍历该节点的儿子,遍历完之后再把这个节点的并查集映射设为他的爸爸,之后遍历他的查询,如果他查询的另一个对象已经查询过了,那么就记录到ans数组里,存为答案。
AC代码如下:
#include <bits/stdc++.h>
using namespace std;
#define N 100
int n, m, cnt2 = 0, cnt = 0, root, qh[N], h[N], fa[N], ans[N];
bool vis[N];
int find(int x)
{
if (x == fa[x])
return x;
return fa[x] = find(fa[x]);
}
struct edge
{
int to, next;
} e[N << 1];
struct q
{
int to, num, next;
} q[N << 1];
void add(int x, int y)
{
e[++cnt].to = y;
e[cnt].next = h[x];
h[x] = cnt;
}
void qq(int x, int y, int num)
{
q[++cnt2].to = y;
q[cnt2].next = qh[x];
q[cnt2].num = num;
qh[x] = cnt2;
}
void tarjan(int u)
{
vis[u] = 1;
for (int i = h[u]; i; i = e[i].next)
if (!vis[e[i].to])
{
tarjan(e[i].to);
fa[e[i].to] = u;
}
for (int i = qh[u]; i; i = q[i].next)
if (vis[q[i].to])
{
ans[q[i].num] = find(q[i].to);
}
}
int main()
{
cin >> n >> m >> root;
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i < n; i++)
{
int x, y;
cin >> x >> y;
add(x, y);
add(y, x);
}
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
qq(x, y, i);
qq(y, x, i);
}
tarjan(root);
for (int i = 1; i <= m; i++)
cout << ans[i] << endl;
return 0;
}