您的位置:首页 > 产品设计 > UI/UE

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

2017-08-18 09:01 375 查看
第一次尝试自己写博客,如果有什么不好的地方还请大家多多批评指正~

传送门:POJ 1986




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

Sample Input
7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6

Sample Output
13
3
36

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

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

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

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

#include<stdio.h>
#include<string.h>
#define MAX 80005

int 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: