2 3 2 1 2 10 3 1 15 1 2 2 3 2 2 1 2 100 1 2 2 1
10 25 100 100
题意:给n个点,n-1条边,m次询问,询问两点之间的最短路。
LCA_Tarjan算法大概流程: 对于新搜索到的一个结点,首先创建由这个结点构成的集合,再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前结点的所有子树搜索完。这时把当前结点也设为已被检查过的,同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。(摘自学长给的资料)
伪代码:
LCA(u) { Make-Set(u) ancestor[Find-Set(u)]=u 对于u的每一个孩子v { LCA(v) Union(u,v) ancestor[Find-Set(u)]=u } checked[u]=true 对于每个(u,v)属于P// (u,v)是被询问的点对 { ifchecked[v]=true then { 回答u和v的最近公共祖先为ancestor[Find-Set(v)] } } }
然后就是Targan,dfs完一个点利用并查集要对该点的所有子树进行合并,且所有祖先都是这个点,这样操作的原因是关于这个点以及子树所有询问要么都已经处理完,要么都在这个点的范围之外。
dfs到一个点就处理关于这个点的询问,但是还有个必要的条件就是与这个点有关的另外一个点必须是被访问过的才能进行处理,这就是为什么在记录询问的时候要双向记录的原因。LCA_Tarjan算法用到深度优先搜索和并查集,使得复杂度只有O(n+q)。
理解不了就弄下来画个图再单步,一步一步理解。
CODE
#include"stdio.h" #include"algorithm" #include"iostream" #include"vector" using namespace std; const int maxn = 80000+10; ///双向边,开40000 TLE 至今搞不懂 struct node ///邻接表 { int to; int w; int next; }edge[maxn]; int n,m; int top; int head[maxn]; int ans[maxn]; ///答案 int dis[maxn]; ///每个点到根节点的权值 int fa[maxn]; ///并查集用 int vis[maxn]; ///标记是否已经访问过 vector <int> query[maxn]; ///记录询问 vector <int> num[maxn]; ///记录询问出现的位置 void INIT() ///初始化 { top = 0; for(int i = 0;i < maxn;i++) { query[i].clear(); num[i].clear(); head[i] = -1; ans[i] = 0; dis[i] = 0; fa[i] = i; vis[i] = false; } } void add(int u,int v,int w) ///加边 { edge[top].to = v; edge[top].w = w; edge[top].next = head[u]; head[u] = top++; } int Find(int x) { if(x != fa[x]) fa[x] = Find(fa[x]); return fa[x]; } void Union(int x,int y) ///并查集合并两个点 { int fx = Find(x); int fy = Find(y); fa[fy] = fx; } void Tarjan(int u,int val) { vis[u] = true; dis[u] = val; for(int i = head[u];i != -1;i = edge[i].next) { int t = edge[i].to; if(vis[t]) continue; ///双向边,dfs时标记了就不会往回递归 Tarjan(t,val+edge[i].w); ///dfs Union(u,t); ///处理完子树就要进行合并 } for(int i = 0;i < query[u].size();i++) ///处理有当前结点的查询 { int t = query[u][i]; if(!vis[t]) continue; ///查询的点未被访问过就不需要更新到ans ans[num[u][i]] = dis[u]+dis[t]-2*dis[Find(t)]; ///ans里面对应需要查询的位置的编号num进行更新 } } int main(void) { int T; scanf("%d",&T); while(T--) { INIT(); scanf("%d%d",&n,&m); for(int i = 1;i < n;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); ///双向边 } for(int i = 1;i <= m;i++) { int q1,q2; scanf("%d%d",&q1,&q2); query[q1].push_back(q2); query[q2].push_back(q1); num[q1].push_back(i); num[q2].push_back(i); } Tarjan(1,0); ///把1设为根节点 for(int i = 1;i <= m;i++) printf("%d\n",ans[i]); } return 0; }