DP之倍增演算法解決LCA問題
LCA,最近公共祖先,也就是樹上兩個節點的相同的祖先裡距離它們最近的(也就是深度最深的)。倍增演算法用於解決LCA問題的線上查詢。
比如要找x和y的LCA,一般會怎麼做?
首先是暴力。一種是DFS遍歷說有點,無需預處理,查詢是O(n)的。還有一種暴力是先一步一步將x和y提到同一高度,然後同時看x和y的父節點,相等則是LCA,不等則讓x=father(x),y=father(y),這個查詢的複雜度顯然是跟x和y距離有關,平均複雜度也是線性的,即O(n)~~
這裡要說的倍增是對第二種暴力的優化,O(1)的查詢——額,看到將O(n)的查詢優化成了O(1),就應該能想到O(n log n)的預處理~~~這裡的倍增也不例外。
首先理解f[i][j]陣列,f[i][j]表示的是結點i的第2^j(這是次方不是異或)代祖先的編號,這個陣列是這個演算法跟DP唯一的關係~~轉移方程很容易就可以寫出來:f[i][j] = f[ f[i][j-1] ][j-1],這很容易理解,因為。理解這個陣列後,預處理就好弄了~預處理主要就是求出每一個節點的深度和f[][]陣列即可~~
首先,讓x和y處於同一高度:因為x和y高度都已知了,只要求出它們的高度差,然後由深度更深的那個倍增查詢直到高度相等即可。如果這不能理解的話,那就舉個例子吧,假設x深度為3,y深度為8,高度差為,也就是說只要依次令y=f[y][2],y=f[y][0]即可(這樣先理解了思想,具體實現後面有程式碼)。
然後,此時x和y高度一樣了,怎麼求公共祖先呢?仍然是用倍增,每次使x=f[x][t],y=f[y][t]~這個t是最大的f[x][t] != f[y][t]的t。顯然t具有單調遞減的特性,所以一次for迴圈,即可求出最後的x和y——最後的x和y也就是最初x和y的所有處於同一深度的祖先裡深度最小的不等的一對(這個不明白就自己想一下吧)。然後它們的父節點就是LCA了。
這個演算法差不多講完了,思想上就是隻要先用倍增使x和y高度一樣,然後使用倍增查詢到最近公共祖先即可。
額,差點忘記了,這個演算法是線上的,所以是支援修改的——允許樹增加葉節點,只需要在加邊的時候求出新節點高度,以及新節點的f陣列即可~~
下面是基本的預處理+查詢的程式碼,至於加邊的就沒寫了(手動滑稽~~~)
/***************************************************************************
*LCA,最近公共祖先的線上演算法——倍增演算法,常用於線上詢問樹上兩點間的距離
*即二者距離最近公共祖先的距離之和,以下程式碼用於解決這個問題
***************************************************************************/
struct Edge // 邊,用於數組裡
{
int e, dis; // edge[i].e表示i與e之間有邊,dis是i和e間的距離
Edge(int ee=0, int diss=0):e(ee), dis(diss) {}
};
vector<Edge> edge[maxn];
int f[maxn][20]; // f[i][j]表示i的第2^j代祖先
int dis[maxn][20]; // dis[i][j]表示i到其第2^j代祖先的距離
int dep[maxn]; // dp[i]表示i的深度
void lca(const int& rt)// nlog n建樹,並得到每一個結點的深度,以及f和dis
{
for(int i = 0; i < edge[rt].size(); ++ i)
{
const int& t = edge[rt][i].e, &d = edge[rt][i].dis;
if(t != f[rt][0])//遍歷每一個和rt相連的非父節點,即rt的子節點
{
f[t][0] = rt; // 第一代祖先,即父節點
dis[t][0] = d; // 與父節點間的距離
dep[t] = dep[rt]+1; // 比父親深度大1
for(int j = 1; (1<<j) < dep[t]; ++ j)
{//求t的f和dis
f[t][j] = f[f[t][j-1]][j-1];
dis[t][j] = dis[t][j-1] + dis[f[t][j-1]][j-1];
}
//深度遍歷rt的子樹
lca(t);
}
}
}
int queue(int x, int y)//查詢節點x和節點y的距離
{
if(dep[x] < dep[y])
swap(x, y);//便於後面將它們提到同一高度
int ret = 0;
for(int i = 19; ~i; -- i)
{// 讓x,y處於同一高度,這個19是因為我f陣列第二維開的20
if(dep[f[x][i]] >= dep[y])
{
ret += dis[x][i];
x = f[x][i];
}
}
if(x != y)
{// x != y 才繼續找公共祖先,因為如果x==y,則表示y是x的祖先,它們的公共祖先就是y,不需要進一步尋找了
for(int i = 19; ~i; -- i)
{
if(f[x][i] != f[y][i])
{
ret += dis[x][i] + dis[y][i];
x = f[x][i];
y = f[y][i];
}
}
ret += dis[x][0] + dis[y][0];
x = f[x][0];
y = f[y][0];
}
return ret;//這裡返回的是距離,如果只是要找祖先,只需要返回x或者y就可以了
}