一、何为在线与离线?
在线算法:指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。
离线算法:在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。
例如,选择排序在排序前就需要知道所有待排序元素,然而插入排序就不必。
因为在线算法并不知道整个的输入,所以它被迫做出的选择最后可能会被证明不是最优的,对在线算法的研究主要集中在当前环境下怎么做出选择。
二、tarjan算法:
LCA:两个点在这棵树上距离最近的公共祖先节点。
Tarjan(离线)算法:先把要求的值存下来,就是所谓的离线一下,,然后结合并查集深搜点,如果所求的两个点都vis[]==1那么输出他们的father。在一次遍历中把所有询问一次性解决,所以其时间复杂度是O(n+q),该算法优点在于相对稳定,时间复杂度也比较居中,也很容易理解。
三、tarjan算法步骤:
1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.用并查集来合并v到u上。
5.寻找与当前点u有询问关系的点v。
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
四、程序代码:
#include<bits/stdc++.h>
using namespace std;
#define N 10005
int in[N],vis[N],fa[N];
int flag,n,qu,qv;
vector<int>V[N];
void init()
{
for(int i=1;i<=n;i++)
{
V[i].clear();
in[i]=0;
fa[i]=i;
vis[i]=0;
}
flag=0;
}
int Find(int x)
{
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
void U(int x,int y)
{
int f1=Find(x);
int f2=Find(y);
fa[f1]=f2;
}
void Tarjan(int u)
{
//cout<<u<<endl;
if(flag)return ;
for(int i=0;i<V[u].size();i++)//
{
int v=V[u][i];
Tarjan(v);
U(v,u);
vis[v]=1;
}
if(flag) return ;
if(u==qu && vis[qv])
{
printf("%d\n",Find(qv));
flag=1;
}
if(u==qv && vis[qu])
{
printf("%d\n",Find(qu));
flag=1;
}
return ;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d",&n);
init();
for(int i=1;i<=n-1;i++)//建立邻接表
{
int u,v;
scanf("%d%d",&u,&v);
in[v]++; //入度+1
V[u].push_back(v);
}
scanf("%d%d",&qu,&qv);
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
Tarjan(i);
break;
}
}
}
}
五、倍增求LCA算法思想
先让它们深度相同,然后倍增向上跳跃(每次跳2的i次方 步,是一种二进制的思想),求出LCA。
六、算法步骤:
1、预处理求出所有结点倍增能达到的点。
2、把询问的两个点移到同一深度。
3、再一步一步求出LCA。
七、算法代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,num=0,head[1000001],dep[1000001],f[1000001][23];
int a1,a2;
struct edg
{
int next,to;
}edge[1000001];
void edge_add(int u,int v)//链式前向星存图
{
num++;
edge[num].next=head[u];edge[num].to=v;head[u]=num;
edge[++num].next=head[v];edge[num].to=u;head[v]=num;
}
void dfs(int u,int father)//对应深搜预处理f倍增达到的点的数组
{
dep[u]=dep[father]+1;
for(int i=1;(1<<i)<=dep[u];i++)
{
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=head[u];i;i=edge[i].next)//遍历所有儿子
{
int v=edge[i].to;
if(v==father)continue;//双向图需要判断是不是父亲节点
f[v][0]=u;
dfs(v,u);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--)//从大到小枚举
{
if(f[x][i]!=f[y][i])//尽可能接近
{
x=f[x][i];y=f[y][i];
}
}
return f[x][0];//随便找一个**输出
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
scanf("%d",&a1);scanf("%d",&a2);
edge_add(a1,a2);//链式存边
}
dfs(s,0);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&a1,&a2);
printf("%d\n",lca(a1,a2));//求两个节点的LCA
}
}
八、倍增时间复杂度为O(nlogn+qlogn)