Problem Description
在Windows下我们可以通过cmd运行DOS的部分功能,其中CD是一条很有意思的命令,通过CD操作,我们可以改变当前目录。
这里我们简化一下问题,假设只有一个根目录,CD操作也只有两种方式:
1. CD 当前目录名\...\目标目录名 (中间可以包含若干目录,保证目标目录通过绝对路径可达)
2. 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操作。
数据保证合法,一定存在一个根目录,每个目录都能从根目录访问到。
每个样例首先一行是两个整数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;
}