每天學一丟之 LCA-Tarjan
阿新 • • 發佈:2019-01-10
每天學一丟意為每天學一點丟一點。
LCA
即樹上最近公共祖先, 可以離線地去求 ,並且可以維護樹上兩個點的距離。
的複雜度是 的
Tarjan
不僅可以解決 問題,還可以解決強連通等其他問題,今天只學會了 問題。
之前在camp中曾經學到過,樹上的 序特別的有用,例如, 正序表示第一次訪問到這個節點,逆序表示訪問到這個節點時,它所有的子樹已經訪問過了。所以在做樹上的時候,這兩個細節對維護樹上的值就顯得特別有用。而 的正序和逆序,可以直接在 的時候就維護出來。就是在剛 到該點時維護,還是遞迴遍歷完所有子節點吼維護的區別。
的基本思想就是並查集 + ,而並查集的作用最主要的是維護當前子樹的根是哪裡。先做一個小小的證明,任意兩點的 說明,這兩點位於該點的兩顆不同子樹上,因為如果兩點同屬於一顆子樹的話,那顆子樹的根就是這兩點的 ,特例是其中一個節點是另一個節點的祖先,但是在這個演算法中並不影響。那麼我們繼續,如果我們訪問到了一個查詢 的 節點,若此時 節點還沒有訪問過,則證明已訪問的子樹當中沒有 ,我們就繼續查到,如果 已經訪問過,那麼此兩點的最近公共祖先就是當前查到的子樹的根。也就是我們 逆序維護並查集,就可以得到當前子樹的根是哪個,然後每次遍歷完所有子樹後,查詢一下包含當前節點的所有詢問就可以了。也是 逆序。
程式碼
vector<pair<int, int> >G[maxn];
map<pair<int, int>, pair<int, int> >ans;
vector<int> ask[maxn];
int vis[maxn], dis[maxn], fa[maxn], x[maxn], y[maxn];
void init(){
ans.clear();
rep(i, 0, maxn) {
G[i].clear();
ask[i].clear();
}
mm(vis, 0);
mm(dis, 0);
}
void addedge(int u, int v, int w){
G[u].push_back(mk(v, w));
G[v].push_back(mk(u, w));
}
void addask(int u, int v){
ask[u].push_back(v);
ask[v].push_back(u);
}
int found(int x){
return x==fa[x]?x:(fa[x]=found(fa[x]));
}
void Tarjan(int u) { //dis[root] = 0;
fa[u] = u, vis[u] = 1;
rep(i, 0, G[u].size()) {
if(!vis[G[u][i].fi]){
dis[G[u][i].fi] = dis[u] + G[u][i].se;
Tarjan(G[u][i].fi);
fa[G[u][i].fi] = u;
}
}
rep(i, 0, ask[u].size()){
if(vis[ask[u][i]]){
int lca = found(ask[u][i]);
int len = dis[u]+dis[ask[u][i]]-2*dis[lca];
ans[mk(u, ask[u][i])] = ans[mk(ask[u][i], u)] = mk(lca, len);
}
}
}
小結
其實就是一個玩出花來的 ,而我的程式碼當中因為使用了,所以複雜度上是多了一個的,一些不太嚴格的資料還是可以過的= =