hdu4547——CD操作 LCA在线算法

Problem Description
  在Windows下我们可以通过cmd运行DOS的部分功能,其中CD是一条很有意思的命令,通过CD操作,我们可以改变当前目录。
  这里我们简化一下问题,假设只有一个根目录,CD操作也只有两种方式:
  
  1. CD 当前目录名\...\目标目录名 (中间可以包含若干目录,保证目标目录通过绝对路径可达)
  2. CD .. (返回当前目录的上级目录)
  
  现在给出当前目录和一个目标目录,请问最少需要几次CD操作才能将当前目录变成目标目录?
 

Input
输入数据第一行包含一个整数T(T<=20),表示样例个数;
每个样例首先一行是两个整数N和M(1<=N,M<=100000),表示有N个目录和M个询问;
接下来N-1行每行两个目录名A B(目录名是只含有数字或字母,长度小于40的字符串),表示A的父目录是B。
最后M行每行两个目录名A B,表示询问将当前目录从A变成B最少要多少次CD操作。
数据保证合法,一定存在一个根目录,每个目录都能从根目录访问到。
 

Output
请输出每次询问的结果,每个查询的输出占一行。
 

Sample Input
  
  
2 3 1 B A C A B C 3 2 B A C B A C C A
 

Sample Output
  
  
2 1 2
 

Source
 



之前学过一个tarjan的离线算法,这次学了个在线的倍增算法,先来简单介绍下这个算法

我们设p[u][i]表示u向上走2^i步后到达的点,那么显然有

p[u][i]=p[p[u][i-1][i-1]成立,意思就是说,u向上走2^i步后到达的点等于y先走2^(i-1)步到达的点再走2^(i-1)步到达的点


每次传入a,b两个点,问a,b的LCA,我们的操作是,先把a,b调到同一高度,那么这个高度就需要对整张图进行一次深搜,并且把p数组计算好,待用

调到同一高度之后,如果两个点相同,那么这就是他们的LCA,否则,他们的LCA一定还在上面,就要继续向上走,具体看代码吧


#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=100010;
const int power=30;
int p[N][30];
int deep[N];
int in[N];
struct node
{
	int to;
	int next;
}edge[N];
int head[N],tot;

void addedge(int from,int to)
{
	edge[tot].to=to;
	edge[tot].next=head[from];
	head[from]=tot++;
}

void dfs(int u,int fa)
{
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa)
			continue;
		deep[v]=deep[u]+1;
		dfs(v,u);
	}
}

int lca(int a,int b)
{
	if(deep[a]<deep[b])
	{
		a^=b;
		b^=a;
		a^=b;
	}
	int d=deep[a]-deep[b];
	for(int i=0;i<power;i++)
	{
		if(d&(1<<i))//这个操作很巧妙,利用2进制的思想
			a=p[a][i];
	}
	if(a==b)
		return a;
	for(int i=power-1;i>=0;i--)
	{
 		if(p[a][i]!=p[b][i])//越过他们的LCA之后,这些p值肯定都是一样的,反之,就一定还没到
		 {
			a=p[a][i];
			b=p[b][i];
		}
	}
	return p[a][0];//如果不明白为什么是这个的话,可以自己在纸上模拟下
	
}

char str1[55],str2[55];

int main()
{
	int n,m;
	int t;
	scanf("%d",&t);
	while(t--)
	{
		memset(in,0,sizeof(in));
	    scanf("%d%d",&n,&m);
	    memset(head,-1,sizeof(head));
	    memset(p,0,sizeof(p));
	    tot=0;
		int cnt=0,i;
		map<string,int>num;
		num.clear();
		for(int i=1;i<=n-1;i++)
		{
			scanf("%s%s",str1,str2);
			if(num[str1]==0)
				num[str1]=++cnt;
			if(num[str2]==0)
				num[str2]=++cnt;
			addedge(num[str2],num[str1]);
			in[num[str1]]++;
			p[num[str1]][0]=num[str2];
		}
		for(i=1;i<=cnt;i++)
			if(in[i]==0)
				break;
		deep[i]=0;
		dfs(i,i);
		for(int j=1;j<power;j++)
			for(int i=1;i<=cnt;i++)
				p[i][j]=p[p[i][j-1]][j-1];
		for(int i=1;i<=m;i++)
		{
			scanf("%s%s",str1,str2);
			int a=num[str1];
			int b=num[str2];
			int c=lca(a,b);
			int ans=deep[a]-deep[c];
			if(c!=b)
				ans++;
			printf("%d\n",ans);
		}
	}
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值