pku 1986 LCA算法的应用

题目大意是在一棵树中,通过提问的方式找出任意两点间的最短距离。对每颗树的提问次数可高达1W次。

分析:

此题高达4W多个点,4W多条边,还有如此高的提问次数,针对题目次数这么多,我们容易想到将每两个点的距离都找出来,以后你问一个我直接作答。

而且更容易想到的是floyd算法,可是我们注意到节点高达4W。肯定超时!

我们要注意这是一棵树,在一棵树中,两个节点通过公共祖先而互达的那条边一定是最短的。那么问题便转化为了求公共祖先。

利用LCA的离线tarjan算法,我们可以轻松的找到任意两点间的祖先,同时在深度遍历的同时,记录所有点到root的距离。最后我们只要输出

该两点离root的距离之和减去两倍的祖先到root的距离,也即:dist[a]+dist[b]-2*dist[lca(a,b)];

还有,该题是通过提问的方式来考察,我们不可能每次都运用tarjan算法找祖先,我们只有把所有问题制成链表,在第一次tarjan中便找出所有问题的祖先节点。

tarjan中,找到某两点的祖先的依据是,当前已遍历到了其中一个点,并且另一个点早也被遍历过,那么沿着任意一个节点往回找,便可以找到祖先。

证明:因为我们对每一个节点开始进行遍历的时候,都是将它做为一个独立的集合来处理,它和它所有的子孙都将被加入这个集合,这所有的都处理完之后,最后我们再决定该集合将属于谁。而我们通过该两点找祖先的事件是发生在该集合合并给其他集合之前,也就是说,他们的祖先节点的pre[x]一定是等于x的!

code

#include<iostream>
#include<string>
using namespace std;

//邻接表建图
typedef struct node
{
	int v;
	int w;
	struct node *next;
}node;

node *link[40005];
node edge[100000];
int num;

void add(int u,int v,int w)
{
	edge[num].v=v;
	edge[num].w=w;
	edge[num].next=link[u];
	link[u]=edge+num++;
	edge[num].v=u;
	edge[num].w=w;
	edge[num].next=link[v];
	link[v]=edge+num++;
}

//提问链表的建立
typedef struct qnode
{
	int v;
	int id; //记录第几个问题
	struct qnode *next;
}qnode;

qnode *link1[40005];
qnode edge1[100000];
int num1;

void add1(int u,int v,int id)
{
	edge1[num1].v=v;
	edge1[num1].id=id;
	edge1[num1].next=link1[u];
	link1[u]=edge1+num1++;
	edge1[num1].v=u;
	edge1[num1].id=id;
	edge1[num1].next=link1[v];
	link1[v]=edge1+num1++;
}

//LCA部分
int n,m;
int v[40005],pre[40005],dist[40005];
int ans[10005];

int find(int x)
{
	if(x!=pre[x])
	{
		pre[x]=find(pre[x]);
	}
	return pre[x];
}

void tarjan(int u,int f)
{
	pre[u]=u; //开始遍历,将该点作为一个独立的集合
	for(node *p=link[u];p;p=p->next)
	{
		if(p->v==f)
			continue;
		if(!v[p->v])
		{
			v[p->v]=1;
			dist[p->v]=dist[u]+p->w;
			tarjan(p->v,u);
			pre[p->v]=u;  //所有子孙添加到该集合
		}
	}
	for(qnode *p1=link1[u];p1;p1=p1->next) //解决可答问题
	{
		if(v[p1->v]) //由于我们一直都在标记父亲节点,此时如果与u相关的问题节点得到了遍历,
		{            //那么就说明找到了u和它的一个问题节点的祖先节点,我们沿着问题节点回溯便是了
			ans[p1->id]=dist[u]+dist[p1->v]-2*dist[find(p1->v)];
		}
	}
}

//主函数
int main()
{
	int t,i,a,b,w;
	char c;
	freopen("D:\\in.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		memset(link,0,sizeof(link));
		num=0;
		for(i=1;i<=m;i++)
		{
			scanf("%d %d %d %c",&a,&b,&w,&c);
			add(a,b,w);
		}
		memset(link1,0,sizeof(link1));
		num1=0;
		scanf("%d",&t);
		for(i=1;i<=t;i++) //建立问题链表
		{
			scanf("%d%d",&a,&b);
			add1(a,b,i);
		}
		memset(v,0,sizeof(v));
		dist[1]=0;
		v[1]=1;
		tarjan(1,1);
		for(i=1;i<=t;i++)
		{
			printf("%d\n",ans[i]);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ka200812/p/2125556.html