乗算アルゴリズム
乗算アルゴリズムは、イデオロギー的な二分法の狭い範囲を使用しています
ノードは、2から、2つのパワー段がすぐにLCAを決定するためにジャンプするように続けることを求めています
これは、LCAを求める共通のツリーノードです-lineアルゴリズムを
乗算アルゴリズムが作成する同一の深さを根ノードの方向への2つのノードをジャンプしながら
最初のノードは、共通の祖先に遭遇するまで、
まあ、これは彼らの先祖ノードの最近の共通の祖先であるノードLCA
ジャンプ中、ステップ番号に2のたびに電力をジャンプ
ジャンプした場合、ルートノードと上記は(もちろん、存在しない)(すなわち、ステップ数よりも大きいが、深さに等しい)ジャンプすることができません
二つのノード又はジャンプの後に同じ場所は、説明がLCAをスキップしていることができる(またはノードがLCA)と、取り扱いが困難で、ジャンプすることができません
ジャンプ停止条件は、2つの隣接ノードの親ノードが同じノードであり、子ノードと二つの位置、即ち、LCA
N個のノードのツリーで
前処理時間の複雑さがあるO(nlogn)
尋ねた時の複雑さは、O(LOGN)
全体的な複雑さは、O((N + Q)LOGN )
乗算アルゴリズムの実装
私たちは今、以下のようにツリーを持っていると仮定
LCA 6問い合わせノードとノード8、
同じ深さを解決しようとすると、すべての最初の2つのノードを作成するように、同じ深さの大きなジャンプノードの祖先ノードの深さと小さなノード位置の深さ
次に始まる降順 3からここに列挙されたステップの数の列挙2倍の電力を、
同時に上方ステップ2 ^ = 8 3を移動させる2つのノードがノードの深さよりも大きい場合、それはジャンプすることができません
2つのノードが同時に2 ^ 2 = 4つのステップまで移動する場合、ノードは、深さよりも大きい場合、ジャンプすることができません
2つのノードが同時に上方ステップ2 ^ 2 = 1に移動し、これは正確であったノードの深さに等しい場合、2つの命令のすべてのルートノードの位置にジャンプした後、それらがジャンプすることができません
ホッピングは、(4-> 26-> 3)異なるジャンプした後、最終的に、2つのノードが同時にノード最大2 ^ 0 = 1ステップ移動した場合見出さ
親は、2つのノード及び3は、それが親ノードを返すことができ、同じである場合、我々が見つかった、LCAが見出さ説明
コードの実装
所与の実施形態のシミュレーションデータ
最初の行は3 NMQの数を与え、それは(そのNを仮定し、Q <= 10000)、Mエッジ、n個のノードがあるq個の質問時間を示します
2つのノード間のエッジの存在を示す二つの数字AB(1≤a、b≤n、≠B)、次のm行
二つの数字AB(1≤a、b≤n)、求めLCA(B)の次Qライン
データ保存方法
const int MAXN=10050,MAXF=16;// MAXF>log2(MAXN)
vector<int> G[MAXN];//存图
int depth[MAXN];//点深度
int father[MAXN][MAXF];//[i][j]为第i个点的距离为2^j的祖先
bool vis[MAXN];//访问标记
MAXFは、本明細書中になければならないよりも大きいステップの可能な最大数の対数を取った値2
父と列挙子手順の背後にあるデジタルスペースの最大範囲のためのアプリケーション
入力処理とデータの呼び出し
int main()
{
int n,m,q,a,b;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);//双向存边
}
dfs(1,0);//从节点1开始深搜处理father数组与depth数组,令1的父节点为0
while(q--)
{
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}
※が存在するDFSは父と深さの深さの各ノード指数関係に対処します
void dfs(int pos,int fa)
{
vis[pos]=true;//标记访问
depth[pos]=depth[fa]+1;//深度为相邻父节点深度+1
father[pos][0]=fa;//2^0=1,所以第0层父节点直接指向相邻父节点
for(int i=1;i<MAXF;i++)
father[pos][i]=father[father[pos][i-1]][i-1];
int cnt=G[pos].size();
for(int i=0;i<cnt;i++)
{
if(!vis[G[pos][i]])
dfs(G[pos][i],pos);//往子树深搜
}
}
祖先ノードvから想定U 2 ^ 5 = 32のステップ
次に、64ステップの祖先ノード、先祖ノードから、すなわちV 2 ^ 5 = 32のステップから6 U ^ 2 =
そして、深さ優先探索ので、そのノードUに検索かつて
それは、そのすべての祖先が検索され、処理されている表し
この場合、反復関係は、直接父[U] [I] =父[V] [I-1]を得ることができます
このとき、U = POS、V =父[POS] [I-1]
従って関係父[POS] [I] =父[父[POS] [I-1]] [I-1]
※共通の祖先を見つけるLCA
int lca(int a,int b)
{
while(depth[a]<depth[b])//如果b的深度比a大
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[b]-(1<<i)>=depth[a])//如果b跳2^i步后深度大于等于a,则可以进行跳跃
b=father[b][i];
}
}
while(depth[a]>depth[b])//如果a的深度比b大
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[a]-(1<<i)>=depth[b])//同上,如果a跳2^i步后深度大于等于b,则可以进行跳跃
a=father[a][i];
}
}
if(a==b)//处理完深度后,如果ab为同一节点,说明原本就在同一条边上,此时直接返回即可
return a;
while(father[a][0]!=father[b][0])//如果与ab相邻的父节点不是同一个节点,说明还需要继续寻找下去
{
for(int i=MAXF-1;i>=0;i--)
{
if(father[a][i]!=father[b][i])//如果跳跃2^i步到达的祖先节点不同的话才能跳跃
{
a=father[a][i];
b=father[b][i];
}
}
}
return father[a][0];//返回此时相邻的父节点作为LCA
}
同じ配列父親によって第1、処置の深さ、下降列挙パワー、各遷移2の電源をステップは、小さい奥行きスキップノードの深さを決定した場合。深さは依然として深さよりも大きい場合に深さまでジャンプに等しい、ノードの小さい方に等しいです。
もともと同じエッジ(上図の例2及び7)に位置する2つのノードであれば
A及びB又は入力(修辞Iと称する)と同じです
== bの後の処理の最初のステップは、Aを設定するので、
この時点で、AまたはBは、直接LCAとして返すことができます
そうしないと、あなたは、2つのノードを見つけるためにジャンプをする必要があります
同じ、降順列挙パワー
季節の親ルート1を検索した後ので、0
ステップの数があまりにもルート・ノード1をスキップした場合ので、その後、父親の配列は、すべて0にセットされます
我々はあまりにも多くの場合に対処する必要はありませんので、
ステップ数は、ルートノードをスキップするほど大きい場合、父[A] [I] ==父[B] [i]が設立== 0
ときに、同じホップを持つ2つのノード、父[A] [I] ==父[B] [i]を設立
すべての望ましくない状況では、このようなA文によって処分することができますので、
ジャンプ条件は、最終的には父である[A] [i]は≠父[B] [i]を得ることができます
LCAに隣接する親ノードとして、最終的に、出力AまたはB
この時点で、でも倍増アルゴリズムを達成するために
完全なコード(テンプレート)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=10050,MAXF=16;
vector<int> G[MAXN];
int depth[MAXN];
int father[MAXN][MAXF];
bool vis[MAXN];
void dfs(int pos,int fa)
{
vis[pos]=true;
depth[pos]=depth[fa]+1;
father[pos][0]=fa;
for(int i=1;i<MAXF;i++)
father[pos][i]=father[father[pos][i-1]][i-1];
int cnt=G[pos].size();
for(int i=0;i<cnt;i++)
{
if(!vis[G[pos][i]])
dfs(G[pos][i],pos);
}
}
int lca(int a,int b)
{
while(depth[a]<depth[b])
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[b]-(1<<i)>=depth[a])
b=father[b][i];
}
}
while(depth[a]>depth[b])
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[a]-(1<<i)>=depth[b])
a=father[a][i];
}
}
if(a==b)
return a;
while(father[a][0]!=father[b][0])
{
for(int i=MAXF-1;i>=0;i--)
{
if(father[a][i]!=father[b][i])
{
a=father[a][i];
b=father[b][i];
}
}
}
return father[a][0];
}
int main()
{
int n,m,q,a,b;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
dfs(1,0);
while(q--)
{
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}
またHDU2586の一例として、
真のテンプレート- HDU2586
#include<iostream>
#include<utility>
#include<vector>
using namespace std;
typedef pair<int,int> P;
const int MAXN=40050,MAXF=18;
vector<P> G[MAXN];//first存id,second存距离
int depth[MAXN];
int father[MAXN][MAXF];
int dis[MAXN];//与根节点距离
bool vis[MAXN];
void dfs(int pos,int fa)
{
vis[pos]=true;
depth[pos]=depth[fa]+1;
father[pos][0]=fa;
for(int i=1;i<MAXF;i++)
father[pos][i]=father[father[pos][i-1]][i-1];
int cnt=G[pos].size();
for(int i=0;i<cnt;i++)
{
if(!vis[G[pos][i].first])
{
dis[G[pos][i].first]=dis[pos]+G[pos][i].second;//先处理子节点的dis
dfs(G[pos][i].first,pos);
}
}
}
int lca(int a,int b)
{
while(depth[a]<depth[b])
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[b]-(1<<i)>=depth[a])
b=father[b][i];
}
}
while(depth[a]>depth[b])
{
for(int i=MAXF-1;i>=0;i--)
{
if(depth[a]-(1<<i)>=depth[b])
a=father[a][i];
}
}
if(a==b)
return a;
while(father[a][0]!=father[b][0])
{
for(int i=MAXF-1;i>=0;i--)
{
if(father[a][i]!=father[b][i])
{
a=father[a][i];
b=father[b][i];
}
}
}
return father[a][0];
}
void solve()
{
int n,q,a,b,d;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
G[i].clear();
vis[i]=false;
}//因为其余数组都是直接覆盖的,没有引用前一次的值,所以不需要初始化
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&d);
G[a].push_back(P(b,d));
G[b].push_back(P(a,d));
}
dfs(1,0);
while(q--)
{
scanf("%d%d",&a,&b);
printf("%d\n",dis[a]+dis[b]-2*dis[lca(a,b)]);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
solve();
return 0;
}