Connections between cities
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 6783 Accepted Submission(s): 1766
Now, your task comes. After giving you the condition of the roads, we want to know if there exists a path between any two cities. If the answer is yes, output the shortest path between them.
5 3 2 1 3 2 2 4 3 5 2 3 1 4 4 5
Not connected 6
之前没做出来,上网找的题解用vector动态存储,据说因为15年杭电换了服务器MTE了,但代码意思很简洁明了。值得参考:http://blog.csdn.net/a601025382s/article/details/10805869
之后改用静态存储,仍然MTE了几次。参考了许多题解,找到了一发没超的。
借鉴一下大神博客里LCA的说明:LCA算法
对于该问题,最容易想到的算法是分别从节点u和v回溯到根节点,获取u和v到根节点的路径P1,P2,其中P1和P2可以看成两条单链表,这就转换成常见的一道面试题:【判断两个单链表是否相交,如果相交,给出相交的第一个点。】。该算法总的复杂度是O(n)(其中n是树节点个数)。
本节介绍了两种比较高效的算法解决这个问题,其中一个是在线算法(DFS+ST),另一个是离线算法(Tarjan算法)。
在线算法DFS+ST描述(思想是:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上):
(1)DFS:从树T的根开始,进行深度优先遍历(将树T看成一个无向图),并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示。
(2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, …, R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。
(3)RMQ:当R[u] ≥ R[v]时,LCA[T, u, v] = RMQ(L, R[v], R[u]);否则LCA[T, u, v] = RMQ(L, R[u], R[v]),计算RMQ。
由于RMQ中使用的ST算法是在线算法,所以这个算法也是在线算法。
【举例说明】
T=<V,E>,其中V={A,B,C,D,E,F,G},E={AB,AC,BD,BE,EF,EG},且A为树根。则图T的DFS结果为:A->B->D->B->E->F->E->G->E->B->A->C->A,要求D和G的最近公共祖先, 则LCA[T, D, G] = RMQ(L, R[D], R[G])= RMQ(L, 3, 8),L中第4到7个元素的深度分别为:1,2,3,3,则深度最小的是B。
离线算法(Tarjan算法)描述:
所谓离线算法,是指首先读入所有的询问(求一次LCA叫做一次询问),然后重新组织查询处理顺序以便得到更高效的处理方法。Tarjan算法是一个常见的用于解决LCA问题的离线算法,它结合了深度优先遍历和并查集,整个算法为线性处理时间。
Tarjan算法是基于并查集的,利用并查集优越的时空复杂度,可以实现LCA问题的O(n+Q)算法,这里Q表示询问 的次数。更多关于并查集的资料,可阅读这篇文章:《数据结构之并查集》。
同上一个算法一样,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)是被询问的点对
{
if checked[v]=true
then {
回答u和v的最近公共祖先为ancestor[Find-Set(v)]
}
}
}
按照以上思想的代码和备注:
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <stack>
#include <cmath>
#include <queue>
#include <list>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
#include <sstream>
#include <iostream>
#include <stack>
using namespace std;
int n,m,q;
struct Node
{
int e,xia,val;
}e1[20001];
struct Node2
{
int e,xia;
}e2[2000001];
int d1,d2,head1[10001],head2[10001];
bool vis[10001];//表示该节点是否被检查过
int fat[10001],dis[10001],ans[1000001];//所有答案记录在ans数组里。fat数组表示父节点。dis数组表示节点到根节点的距离。
void add1(int s,int e,int val){
e1[d1].e=e,e1[d1].val=val,e1[d1].xia=head1[s];//表示d1个输入组合中,由s点->e点。head1[s]表示对点的一个索引,因为hear1[s]=d1,与e1[d1]的d1是相同的。
head1[s]=d1++;//此处s表示的点,但索引的痕迹是d1。通过d1才能索引至e1[d1].e,实现s点->e点的指向。
//仔细思考可以发现xia表示的是s点的其他子树,当s->e时,s无除e以外的子树时,毫无疑问xia就-1,否则,xia为该子树的d1。
}
void add2(int s,int e){
e2[d2].e=e,e2[d2].xia=head2[s];
head2[s]=d2++;
}
int find(int num){
if(fat[num] == num)return num;
return fat[num]=find(fat[num]);
}
void Union(int s,int e){
fat[e]=s;
}
void tarjan(int s){
vis[s]=true;
for(int i=head1[s];i != -1;i=e1[i].xia){//开始对s点进行深度遍历,如果s点的子节点未被检查过,则可相应计算出子节点dis,并递归继续进行深度遍历,同时将s的子节点的父节点表示为s。
if(!vis[e1[i].e]){
dis[e1[i].e]=dis[s]+e1[i].val;
tarjan(e1[i].e);
Union(s,e1[i].e);
}
}//从此循环外层随着xia遍历,内层递归与s点连接的点可看出为深度遍历
for(int i=head2[s];i != -1 ;i=e2[i].xia){
if(vis[e2[i].e]){
if(dis[e2[i].e] != -1){//此处需注意,在main中进入trajan之前会将dis清空,若不为-1,则必在同一子树内。
ans[i/2]=dis[s]+dis[e2[i].e]-2*dis[find(e2[i].e)];
}
else ans[i/2]=-1;
}
}//此循环发生在s点的深度遍历之后。s点深度遍历已经将s的子树节点计算合并完毕,之后在此遍历中处理询问事项。已知此循环中任意相连两点均为询问内容,只需考虑这两点是否在同一子树中。若存在同一子树中,分别已知两点到根节点距离,相加后减去最近公共父节点即可。若不在同一子树中,自然无法连接。
}
int main(){
int x,y,z;
while(~scanf("%d %d %d",&n,&m,&q)){
d1=d2=0;
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
for(int i=0;i<m;i++){
scanf("%d %d %d",&x,&y,&z);
add1(x,y,z);
add1(y,x,z);
}
for(int i=0;i<q;i++){
scanf("%d %d",&x,&y);
add2(x,y);
add2(y,x);
}
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)fat[i]=i;
for(int i=1;i<=n;i++){
if(!vis[i])
{
memset(dis,-1,sizeof(dis));
dis[i]=0;
tarjan(i);
}
}
for(int i=0;i<q;i++){
if(ans[i] == -1)puts("Not connected");
else printf("%d\n",ans[i]);
}
}
return 0;
}