POJ 1986 Distance Queries Tarjan算法求最近公共祖先+前向星

来源:互联网 发布:爱奇艺会员淘宝购买 编辑:程序博客网 时间:2024/06/05 06:23

第一次尝试自己写博客,如果有什么不好的地方还请大家多多批评指正~


传送门:POJ 1986


题目大意:John是一个农场主,他的牛都懒散惯了,所以拒绝按照John所选的路走。John就想找一条最短的路。本题输入前半部分与“导航噩梦”相同,在每组数据之后会有一个整数K,接下来K行每行一个“距离询问”。每个距离询问包含两个整数,表示John想要计算距离的两个农场的编号。


Sample Input

7 61 6 13 E6 3 9 E3 5 7 S4 1 3 N2 4 20 W4 7 2 S31 61 42 6

Sample Output

13336

思路:因为是求任意两个农场之间的距离,所以第一想法是用Floyd算法求多源最短路。但是该算法时间复杂度为O(n^3),有1w组询问,会超时。经过分析我们发现题目给出的方向完全没影响,可以忽略。并且两个节点之间至多有1条路,根据数据建图一定是一颗树。所以我们可以先把任一节点作为根节点,用深搜获取每个节点到根节点的距离,然后用Tarjan算法求最近公共祖先以及两点间的距离。


具体实现时深搜求距离和Tarjan算法求最近公共祖先可以同时进行,由于储存点的时候既要方便遍历与某节点相邻的所有节点,又要存储边权,同时最好能直接通过下标来访问数据。一般的结构体或者vector数组就不好用了。这里采用了“前向星”的数据结构。ACdreamers大牛讲解前向星 点击此处可以查看前向星的知识,非常好用的一种数据结构,建议掌握。至于Tarjan算法的实现,利用了并查集和前向星的知识,不懂的自行百度吧~


递归求节点i到根节点的距离时,公式为:节点i对应起点到根节点的距离 + 起点到i的距离。对于所要求的两点间的距离公式为:两点到根节点的距离和 - 2 * 两点的最近公共祖先到根节点的距离,不懂的画个图就可以明白了。


具体实现还是看代码吧,里面给了比较详细的注释,若还看不懂欢迎提问。

#include<stdio.h>#include<string.h>#define MAX 80005int id,iq;  //分别记录存储的点和询问的个数 int f[MAX],vis[MAX],dis[MAX]; //dis[i]记录根节点到i的距离 //head[i]记录以i为起点的第一条边的下标,qhead类似 int head[MAX],qhead[MAX];struct node{ //前向星 int w;      //两点间权值 int to;     //终点 int next;   //和to起点相同的下一条边的存储下标 } edge[MAX],que[MAX];void add_edge(int u,int v,int w) { //加点 edge[id].to=v;edge[id].w=w;edge[id].next=head[u];head[u]=id++;edge[id].to=u;edge[id].w=w;edge[id].next=head[v];head[v]=id++;}void add_que(int u,int v){ //加询问 que[iq].to=v;que[iq].next=qhead[u];qhead[u]=iq++;que[iq].to=u;que[iq].next=qhead[v];qhead[v]=iq++;}void init(int n){ //并查集的初始化函数int i;for(i=0;i<=n;i++) f[i]=i;memset(vis,0,sizeof(vis));memset(dis,0,sizeof(dis));memset(head,-1,sizeof(head));    //head初始化为-1,表示无下一条边 memset(qhead,-1,sizeof(qhead));}int find(int x){ //并查集的压缩路径版查找函数if(x!=f[x]) f[x]=find(f[x]);return f[x];}void Tarjan(int root){int i;vis[root]=1;f[root]=root;for(i=head[root];i!=-1;i=edge[i].next){ //不断搜索以当前顶点为起点的节点 if(!vis[edge[i].to]){//根节点到终点的距离=根节点到起点的距离 + 边权 dis[edge[i].to]=dis[root]+edge[i].w;Tarjan(edge[i].to);f[edge[i].to]=root;}}for(i=qhead[root];i!=-1;i=que[i].next){ //查询和当前节点有关的询问 if(vis[que[i].to]){//两点间距离为两点到根节点的距离和 - 两倍的最近总共祖先到根节点距离 que[i].w=dis[root]+dis[que[i].to]-2*dis[find(que[i].to)];que[i^1].w=que[i].w;  //第i和i+1的结果相同(i为偶数) }}}int main(){int i,t,n,m,k,u,v,w;scanf("%d%d",&n,&m); //顶点数和边数 init(n);id=0;for(i=0;i<m;i++){scanf("%d%d%d%*c%*c",&u,&v,&w); //两节点及其之间的权值 add_edge(u,v,w);}scanf("%d",&k);  //询问数 iq=0;for(i=0;i<k;i++){scanf("%d%d",&u,&v);add_que(u,v);}Tarjan(1); //选节点1作为根节点for(i=0;i<iq;i+=2) printf("%d\n",que[i].w);return 0;}

阅读全文
1 0