每天学一丢意为每天学一点丢一点。
LCA
LCA
L
C
A
即树上最近公共祖先,
Tarjan
T
a
r
j
a
n
可以离线地去求
LCA
L
C
A
,并且可以维护树上两个点的距离。
Tarjan
T
a
r
j
a
n
的复杂度是
O(n+q)
O
(
n
+
q
)
的
Tarjan
Tarjan
T
a
r
j
a
n
不仅可以解决
LCA
L
C
A
问题,还可以解决强连通等其他问题,今天只学会了
LCA
L
C
A
问题。
之前在camp中曾经学到过,树上的
dfs
d
f
s
序特别的有用,例如,
dfs
d
f
s
正序表示第一次访问到这个节点,
dfs
d
f
s
逆序表示访问到这个节点时,它所有的子树已经访问过了。所以在做树上
dp
d
p
的时候,这两个细节对维护树上的值就显得特别有用。而
dfs
d
f
s
的正序和逆序,可以直接在
dfs
d
f
s
的时候就维护出来。就是在刚
dfs
d
f
s
到该点时维护,还是递归遍历完所有子节点吼维护的区别。
Tarjan T a r j a n 的基本思想就是并查集 + dfs d f s ,而并查集的作用最主要的是维护当前子树的根是哪里。先做一个小小的证明,任意两点的 LCA L C A 说明,这两点位于该点的两颗不同子树上,因为如果两点同属于一颗子树的话,那颗子树的根就是这两点的 LCA L C A ,特例是其中一个节点是另一个节点的祖先,但是在这个算法中并不影响。那么我们继续,如果我们访问到了一个查询 (u,v) ( u , v ) 的 u u 节点,若此时 节点还没有访问过,则证明已访问的子树当中没有 v v ,我们就继续查到,如果 已经访问过,那么此两点的最近公共祖先就是当前查到的子树的根。也就是我们 dfs d f s 逆序维护并查集,就可以得到当前子树的根是哪个,然后每次遍历完所有子树后,查询一下包含当前节点的所有询问就可以了。也是 dfs d f s 逆序。
代码
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);
}
}
}
小结
Tarjan T a r j a n 其实就是一个玩出花来的 dfs d f s ,而我的代码当中因为使用了 map m a p ,所以复杂度上是多了一个 log log 的,一些不太严格的数据还是可以过的= =