最近公共祖先·三 (LCA線上演算法)
描述
上上回說到,小Hi和小Ho使用了Tarjan演算法來優化了他們的“最近公共祖先”網站,但是很快這樣一個離線演算法就出現了問題:如果只有一個人提出了詢問,那麼小Hi和小Ho很難決定到底是針對這個詢問就直接進行計算還是等待一定數量的詢問一起計算。畢竟無論是一個詢問還是很多個詢問,使用離線演算法都是隻需要做一次深度優先搜尋就可以了的。
那麼問題就來了,如果每次計算都只針對一個詢問進行的話,那麼這樣的演算法事實上還不如使用最開始的樸素演算法呢!但是如果每次要等上很多人一起的話,因為說不準什麼時候才能夠湊夠人——所以事實上有可能要等上很久很久才能夠進行一次計算,實際上也是很慢的!
“那到底要怎麼辦呢?在等到10分鐘,或者湊夠一定數量的人兩個條件滿足一個時就進行運算?”小Ho想出了一個折衷的辦法。
“哪有這麼麻煩!別忘了和離線演算法相對應的可是有一個叫做線上演算法的東西呢!”小Hi笑道。
小Ho面臨的問題還是和之前一樣:假設現在小Ho現在知道了N對父子關係——父親和兒子的名字,並且這N對父子關係中涉及的所有人都擁有一個共同的祖先(這個祖先出現在這N對父子關係中),他需要對於小Hi的若干次提問——每次提問為兩個人的名字(這兩個人的名字在之前的父子關係中出現過),告訴小Hi這兩個人的所有共同祖先中輩分最低的一個是誰?
提示:最近公共祖先無非就是兩點連通路徑上高度最小的點嘛!×Close
提示:最近公共祖先無非就是兩點連通路徑上高度最小的點嘛!
“那你快教我啊!”小Ho耐不住性子。
“不要急,且聽我緩緩道來,還記得很久之前我和你說過的最近公共祖先其實就是這兩個點連通路徑上的那個折點麼(參見hiho一下第十一週樹的直徑)”小Hi問道。
“記得!”
“這個折點也就是這2點所連路徑上深度最小的那個點了!那麼這個問題其實和我們之前所提到的那個求區間最小值的是不是差不多(參見hiho一下第十六週——RMQ-ST演算法),只不過一個是在陣列上的區間,一個是在樹上的區間?”小Hi問道。
“你非要這麼說那我只能說是啦。。但是樹和陣列還是差了挺遠的吧。”小Ho表示汗顏。
小Hi點了點頭,隨即道:“那就這麼弄一下,我從樹的根節點開始進行深度優先搜尋,每次經過某一個點——無論是從它的父親節點進入這個點,還是從它的兒子節點返回這個點,都按順序記錄下來
小Ho顯然是沒有料到小Hi還有這一招,一上來也是感覺明顯就不對嘛,畢竟好好的樹怎麼隨便就弄成陣列了不是,但是靜下心來仔細想想:“從第一個點離開(返回它的父親節點),到從第二個點離開(返回它的父親節點)的這一段路程,的確經過的深度最小的點就是‘最近公共祖先’這一個點!”
看著小Ho露出了驚訝的神情,小Hi滿意的點了點頭,道:“這就是一個很好的將樹轉換成陣列來進行某些特殊演算法的方法!而且你仔細看看就會發現轉換出的陣列的長度其實就是邊數的2倍而已,也是O(n)的級別呢~”
“原來是這樣!那這次我只需要簡單的套用之前寫的演算法,很簡單嘛!”小Ho笑道。
“那是自然,你也不看看之前我們積累了一個月呢,現在你要是還磨磨蹭蹭的,回國怎麼向河蟹先生交代!”
“嘿嘿嘿……”
Close
輸入
每個測試點(輸入檔案)有且僅有一組測試資料。
每組測試資料的第1行為一個整數N,意義如前文所述。
每組測試資料的第2~N+1行,每行分別描述一對父子關係,其中第i+1行為兩個由大小寫字母組成的字串Father_i, Son_i,分別表示父親的名字和兒子的名字。
每組測試資料的第N+2行為一個整數M,表示小Hi總共詢問的次數。
每組測試資料的第N+3~N+M+2行,每行分別描述一個詢問,其中第N+i+2行為兩個由大小寫字母組成的字串Name1_i, Name2_i,分別表示小Hi詢問中的兩個名字。
對於100%的資料,滿足N<=10^5,M<=10^5, 且資料中所有涉及的人物中不存在兩個名字相同的人(即姓名唯一的確定了一個人),所有詢問中出現過的名字均在之前所描述的N對父子關係中出現過,且每個輸入檔案中第一個出現的名字所確定的人是其他所有人的公共祖先。
輸出
對於每組測試資料,對於每個小Hi的詢問,按照在輸入中出現的順序,各輸出一行,表示查詢的結果:他們的所有共同祖先中輩分最低的一個人的名字。
Sample Input
4 Adam Sam Sam Joey Sam Micheal Adam Kevin 3 Sam Sam Adam Sam Micheal Kevin
Sample Output
Sam Adam Adam
描述
https://www.cnblogs.com/cuimama/p/9446570.html
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
#include<vector>
#include<math.h>
#define maxn 200010
using namespace std;
map<string,int>mp;
int n,m,tot;
string name[maxn];
vector<int>v[maxn];
int root;
int f[maxn],dep[maxn],pos[maxn];
int dp[maxn][100];
void init(){
memset(dp,0,sizeof(dp));
memset(pos,0,sizeof(pos));
memset(f,0,sizeof(f));
memset(dep,0,sizeof(dep));
// mp.clear();
// for(int i=0;i<maxn;i++) v[i].clear();
}
void dfs(int idx,int depth)
{
f[++tot]=idx;
dep[tot]=depth;
pos[idx]=tot;
for(int i=0;i<v[idx].size();++i){
dfs(v[idx][i],depth+1);
f[++tot]=idx;
dep[tot]=depth;
}
}
void st()
{
for(int i=1;i<=tot;++i){
dp[i][0]=i;
}
for(int j=1;(1<<j)<=tot;++j){
for(int i=1;i+(1<<j)-1<=tot;++i){
int mid=i+(1<<(j-1));
if(dep[dp[i][j-1]]<dep[dp[mid][j-1]]){
dp[i][j]=dp[i][j-1];
}else{
dp[i][j]=dp[mid][j-1];
}
}
}
}
int rmq(int l,int r)
{
l=pos[l];
r=pos[r];
if(l>r)
swap(l,r);
int len=r-l+1;
int len2=log2(len);
if(dep[dp[l][len2]]<dep[dp[r-(1<<len2)+1][len2]])
return dp[l][len2];
return dp[r-(1<<len2)+1][len2];
}
int main()
{
int n;
while(~scanf("%d",&n))
{
//init();
string s1,s2;
for(int i=0;i<n;i++){
cin>>s1>>s2;
if(!mp[s1]){
mp[s1]=++tot;
name[mp[s1]]=s1;
}
if(!mp[s2]){
mp[s2]=++tot;
name[mp[s2]]=s2;
}
v[mp[s1]].push_back(mp[s2]);
if(!i){
root=mp[s1];
}
}
tot=0;
dfs(root,0);
st();
int m;
scanf("%d",&m);
while(m--){
cin>>s1>>s2;
int aa = mp[s1], bb = mp[s2];
int ans=rmq(aa,bb);
cout<<name[f[ans]]<<endl;
}
}
return 0;
}