记录一个菜逼的成长。。
/***update 2017.5.11***/
对于查找一棵树的LCA有四种算法。
第一种,暴力搜索。
对于数据很小的题可以这样写。
先标记其中一个人的所有祖先,再从第二个人开始往上,向他的父亲搜索,直到搜索到第一个被标记的人,这个人就是他们两个人的最近公共祖先。
第二种,基于tarjan的离线算法。是将所有的查询都整理在一起,在去搜索判断,运用并查集找到最近公共祖先。
整理好所有的查询。
query[a].push_back(b);
query[b].push_back(a);
从根节点开始搜索。
如果有儿子一直往下搜索。
回溯合并集合。
回溯标记搜索过的。
对于回溯的每一个点,判断和这个点一对的另一个是否被标记过,如果是,他们的最近公共祖先就是另一个点的集合的当前根节点。如果不是,则不理这个点,继续搜索。
void unio(int x,int y)
{
int fx = findr(x),fy = findr(y);
if(fx != fy)pre[fy] = fx;
}
void tarjan(int u)
{
for( int i = head[u]; ~i; i = edge[i].next ){
int v = edge[i].to;
tarjan(v);
unio(u,v);//回溯合并
}
vis[u] = 1;
for( int i = 0; i < query[u].size(); i++ ){
if(vis[query[u][i]]){
ancestor[u] = findr(query[u][i]);
}
}
}
具体可参考:http://hihocoder.com/contest/hiho15/problem/1
第三种,基于ST算法的在线算法。
简单介绍ST算法
是用来解决RMQ问题,即区间最值查询问题
dp[i][j] := 从下标i开始长度为2^j的最小值
显然dp[i][0] = a[i];
那么通过二分区间 ,以最小值为例
dp[i][j] = min(dp[i][j-1],dp[i+(1 << j - 1)][j-1];
void ST()
{
for( int i = 0; i < n; i++ ) ///下标从0开始
dp[i][0] = a[i];
for( int j = 1; (1 << j) <= n; j++ ){
for( int i = 0; i + (1 << j) - 1 < n; i++ ){
dp[i][j] = min(dp[i][j - 1],dp[i + (1 << j - 1)][j - 1]);
//查询最小值,改为max则最大值
}
}
}
LCA
先通过dfs得到点的序列号、每一个点的第一次出现的序列号和每一个序列号的深度.
void dfs(int x,int dep)
{
first[x] = ind;
depth[ind] = dep;
a[ind++] = x;
for( int i = head[x]; ~i; i = edge[i].next ){
int v = edge[i].to;
dfs(v,dep+1);
depth[ind] = dep;
a[ind++] = x;
}
}
再对深度用ST算法计算
并同时保存当前最小深度的序列号
void ST(int n)
{
for( int i = 0; i < n; i++ ) ///下标从0开始
///first 为深度 second 为 序列号即下标
dp[i][0].first = depth[i],dp[i][0].second = i;
for( int j = 1; (1 << j) <= n; j++ ){
for( int i = 0; i + (1 << j) - 1 < n; i++ ){
if(dp[i][j-1].first < dp[i+(1<<j-1)][j-1].first){
dp[i][j].first = dp[i][j-1].first;
dp[i][j].second = dp[i][j-1].second;
}
else {
dp[i][j].first = dp[i+(1<<j-1)][j-1].first;
dp[i][j].second = dp[i+(1<<j-1)][j-1].second;
}
}
}
}
然后对first[u] 和 first[v]进行判断 ,查询。
如果first[u] < first[v] ,说明在访问到v点是u点已访问过(可联系第二种tarjan算法的并查集过程理解)。
只要知道在first[u]~first[v]之间的最小深度的序列号
int query(int u,int v)
{
if(first[u] > first[v])swap(u,v);
int w = first[v] - first[u] + 1;
int k = (int)(log(w*1.0) / log(2.0));
return dp[first[u]][k].first < dp[first[v]-(1<<k)+1][k].first ? dp[first[u]][k].second : dp[first[v]-(1<<k)+1][k].second;
}
第四种 倍增法求
LCA
相关知识
int deep[maxn];//每个点的深度
//ans[i][j] :=表示i点的2^j个祖先,,1倍(j==0)祖先是i点的父亲,2倍(j==1)祖先是i的父亲的父亲,以此类推
int anc[maxn][20];
//处理处每个点的深度和父亲
void dfs(int u,int pre,int d)
{
deep[u] = d;
for( int i = head[u]; ~i; i = edge[i].next ){
int v = edge[i].to;
if(v == pre)continue;
anc[v][0] = u;
dfs(v,u,d+1);
}
}
void Init()
{
for( int j = 1; (1<<j) <= n; j++ ){
for( int i = 1; i <= n; i++ ){
if(anc[i][j-1] != -1){
//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
anc[i][j] = anc[anc[i][j-1]][j-1];
}
}
}
}
/*
从深度大的节点上升至深度小的节点同层,如果此时两节点相同直接返回此节点,即lca。
否则,利用倍增法找到最小深度的anc[a][j]!=anc[b][j],此时他们的父亲anc[a][0]即lca。
*/
int lca(int a,int b)
{
int i,j;
if(deep[a] < deep[b])swap(a,b);
for( i = 0; (1<<i) <= deep[a]; i++ );
i--;
//使a和b的深度相等 (这里运用了二进制的思想,比如,1,2,4可以表示出7以内的所有正整数
for( j = i; j >= 0; j-- ){
if(deep[a] - (1<<j) >= deep[b]){
a = anc[a][j];
}
}
if(a == b)return a;
for( j = i; j >= 0; j-- ){
if(anc[a][j] != -1 && anc[a][j] != anc[b][j]){
a = anc[a][j];
b = anc[b][j];
}
}
return anc[a][0];
}