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

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


传送门: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值